引言

适用范围

本文档适用于集成主数据管理平台的三方应用系统(供开发者参考),文档的适用产品版本为:7.1 LA

编写目的

本文档主要介绍主数据管理平台提供的服务接口以及调用方式。

读者

本文档的适用读者范围主要是应用系统接入主数据管理平台过程中的设计和开发人员。

服务接口说明

  • swagger文档参考 http://<ip>:<port>/swagger-ui.html
  • 统一使用RESTFul + JSON格式
  • API调用认证统一使用用户TOKEN(先调用登录接口获取用户TOKEN)

获取API Token

获取key

API:GET /api/afc/login/password/key,请求头Content-Type: application/json, 响应报文参考:

{
"data":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/zpAB1JB5KJFd7KrtudeIvv02Q0XdJqpwsxTL2zXzjU3pB8pa5pshFUFXD/W++4/Ha+/gAqOxRZ4dt3SI0bLXlGGka4H6LMp+fFbZt9k/ym0rR2V9wW8xzxcfuYNxQMx20cvZxPCndc4bG8WLvDkSByr9epM2FrtL6b5xRBpsRQIDAQAB",
"type":"RSA"
}

登录API

API:POST /api/afc/login,请求头Content-Type: application/json,请求报文示例:

{
"user":{
"code":"jianggangli-admin",
"password":"r1JblLSK+L0dqSxyYqsju9IG7BWPieYOr4OVolzkTROadC/zzM8+dEUhR6Jhz54yuoK4SqQ+lTB29LebHiW4qde3EGKm1H5bM+hcvzeP0EzZgBF3t9Zi9fK46fT+ue/lcDCJutQhgtYLR2XBBNy0Via2xWL733vufzSRdwTwHL4="
},
"verifyCode":"qj3c"
}

无响应报文

业务表APIs

根据主键获取单条数据

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID
  • 响应报文示例:
{
  "M_ID": "cdb5b373-1772-41a6-8ec2-8970b6ca067b",
  "M_MODEL_ID": "c48da360-94a9-4201-b0e0-cddcb2555734",
  "M_CREATED_AT": "2021-03-11 15:39:08",
  "M_CREATED_BY": "lizw",
  "M_DATA_FROM": "mdm::manual",
  "M_DATA_STATE": 0,
  "M_DATA_UID": "1080ffdc-21e4-486f-9a71-74d9db2cbdf6",
  "M_DATA_VERSION": 1,
  "M_LAST_MODIFIED_AT": "2021-03-11 15:39:16",
  "M_LAST_MODIFIED_BY": "lizw",
  "M_MODEL_VERSION": 1,
  "M_SECURITY_LEVEL": 1,
  "foo": "f1",
  "gender": "male",
  "name": "赵子龙"
}

【备注】以"M_"开头的字段为主数据管理平台的系统字段

批准待审批的数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/approve/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID(待审态数据ID集合)

批量删除数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-delete
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(编辑态数据ID集合)
Content-Type Header String application/json

批量作废数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-deprecate
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(发布态数据ID集合)
Content-Type Header String application/json

批量停用数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-disable
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(发布态数据ID集合)
Content-Type Header String application/json

批量启用数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-enable
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(停用态数据ID集合)
Content-Type Header String application/json

批量插入数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-insert
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
data Body Map[] 数据数组
Content-Type Header String application/json

数据字段请使用数据模型中所定义的那些字段,请求报文体参考示例:

[{
  "name": "张飞",
  "gender": "male"
}, {
  "name": "赵云",
  "gender": "male"
}, {
  "name": "张春华",
  "gender": "female"
}, {
  "name": "孙尚香",
  "gender": "female"
}]

批量提交送审数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-pending
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(编辑态数据ID集合)
Content-Type Header String application/json

批量批准待审批(送审)的数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-approve
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(待审态数据ID集合)
Content-Type Header String application/json

批量驳回(拒绝)待审批(送审)的数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-reject
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(待审态数据ID集合)
Content-Type Header String application/json

批量发布数据(编辑态->发布态,无需审核)

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-release
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(编辑态数据ID集合)
Content-Type Header String application/json

批量修订数据(批量克隆发布态数据)

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/batch-revise
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
ids Body String[] 数据ID数组(发布态数据ID集合)
Content-Type Header String application/json

复制单条数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/copy/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

响应报文内容为新数据的ID。

删除某模型下的文件

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/delete-data-files
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
fileIds Body String[] 文件ID数组
Content-Type Header String application/json

删除单条数据

  • 方法(Method):DELETE
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/delete/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

作废单条数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/deprecate/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

停用单条数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/disable/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

启用单条数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/enable/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

下载文件

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/download-data-file
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
fileId Query String 文件ID

下载导入数模板

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/download-template
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
autoRelease Query boolean 是否同时生效
file Path File 文件名

批量更新数据及是否生效

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/update
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码

导出数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/export
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
pageNum Query Integer 分页页数
pageSize Query Integer 分页大小
param Body MDMQueryParam 查询参数
Content-Type Header String application/json

导入数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/import
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
autoRelease Path boolean 数据模型编码
autoExport Path boolean 数据模型编码
file File Form File 上传文件(Excel)

插入单条数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/insert
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
data Body Map JSON数据
Content-Type Header String application/json

请求报文参考示例:

{
  "name": "张春华",
  "gender": "female"
}

更新单条数据

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/update/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID
data Body Map JSON数据
isAllRefData Query Boolean 默认:true, 要保存的数据里是否有全量的RefData(对应属性为_M_REF_DATA_MAP), 如果是全量, 则会按照传入的_M_REF_DATA_MAP和数据库已有的数据比较, 做新增/更新/删除; 如果不是全量, 则只做新增/更新
Content-Type Header String application/json

请求报文参考示例:

{
  "name": "张春华",
  "gender": "female"
}

上传指定数据字段对应的文件

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/insert-data-files
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
fieldCode Form/Query String 字段编码
files File Form File 上传文件集合 (multipart/form-data)
id Form/Query String 目标数据

响应报文参考格式:

[
  {
    "dataId": "uuid-string",
    "fileName": "name-string",
    "fileSize": 1002340,
    "fileSuffix": "string",
    "id": "uuid-string",
    "modelFieldCode": "string",
    "modelId": "uuid-string"
  }
]

送审单条数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/pending/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

推送数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/push
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
pushData Body Map 推送目标和内容,dataUids数据M_ID数组,toPushIds订阅者ID数组

请求报文格式参考:

{
  "dataUids": [
    "string"
  ],
  "toPushIds": [
    "string"
  ]
}

驳回(拒绝)单条待审态数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/reject/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

发布单条编辑态数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/release/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

修订单条发布态数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/revise/{id}
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
id Path String 数据ID

查询指定的数据字段对于的文件

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/query-data-files
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
fieldCode Query String 字段编码
id Query String 数据ID

响应报文结构参考:

[
  {
    "dataId": "string",
    "fileName": "string",
    "fileSize": 0,
    "fileSuffix": "string",
    "id": "string",
    "modelFieldCode": "string",
    "modelId": "string"
  }
]

查询指定的数据字段引用的其他模型的数据

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/query-ref-datas
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
fieldCode Query String 字段编码
id Query String 数据ID
refModelId Query String 引用模型ID
isDataMasking Query Boolean 是否脱敏(默认为true)

响应报文结构参考:

[{
  "prop1": "value",
  "prop2": "value"
}, {
  "prop1": "value",
  "prop2": "value"
}]

新增、更新指定的数据字段引用的其他模型的数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/save-ref-datas
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
fieldCode Query String 字段编码
id Query String 数据ID
refModelId Query String 引用模型ID
refDatas Body Map[] 引用模型数据集合

请求报文结构参考:

[{
  "prop1": "value",
  "prop2": "value"
}, {
  "prop1": "value",
  "prop2": "value"
}]

数据查询接口 — POST方式

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/query
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
withSysColumn Query Boolean 查询结果是否包含系统字段
pageNum Query Integer 分页页数
pageSize Query Integer 分页大小
param Body MDMQueryParam 查询参数
Content-Type Header String application/json

请求报文体结构:(报文体的所有参数都是可选参数)

{
  "and": true,
  "args": [
    {
      "matchValue": "value1",
      "name": "column1"
    }
  ],
  "columns": [
    "column1", "column2", "column3", "column4"
  ],
  "parseRef": {
    "parseRefDictField": true,
    "parseRefOrdinaryField": true,
    "parseRefReferenceField": false,
    "parseRefWithSuffix": "__ref",
    "replaceRefDictValue": false,
    "replaceRefOrdinaryValue": false
  },
  "sorts": [
    {
      "asc": true,
      "name": "column1"
    }, {
      "asc": false,
      "name": "column2"
    }
  ]
}

请求报文体参考示例:

{
    "args": [{
        "name": "name",
        "matchValue": "zhang",
        "matchType": "like",
        "matchOption": "starts-with"
    }, {
        "name": "age",
        "matchValues": [ "20", "30" ],
        "matchType": "between"
    }],
    "sorts": [{
        "name": "age",
        "asc": false
    }, {
        "name": "name"
    }],
    "and": true
}

多条件连接逻辑 — QueryParam#and

缺省AND连接。

查询结排序 - QueryParam#sorts

缺省不排序,按照用户指定的字段进行排序。

查询结果列 - QueryParam#columns

缺省调用者拥有所有权限的那些字段,可能再加上系统字段(withSysColumn=true)。

查询结果的列数据脱敏 — QueryParam#desensitization

Boolean, 缺省不进行数据脱敏处理 (false)。

查询结果的引用字段转换 — QueryParam#parseRef

引用字段查询转换、默认开启,可以通过以下方式关闭;

parseRefDictField=false
parseRefOrdinaryField=false
parseRefReferenceField=false

# 引用字段值替换默认关闭,新增一个`${field}__ref`字段存入实际值,可以通过以下方式开启;
replaceRefDictValue=true
replaceRefOrdinaryValue=true

# 缺省新字段后置为`__ref`,可以通过参数`parseRefWithSuffix`进行自定义;
parseRefWithSuffix=__ref

支持查询条件匹配的5大类型

eq, ne, gt, ge, lt, le,
like, notlike
between, notbetween
in, notin
isnull, notnull
  • 单一值:eq, ne, gt, ge, lt, le,使用matchValue存放值,所有可查询的字段均使用String类型存放
  • 模糊匹配:like, notlikematchOption缺省值containsmatchValue存放模糊匹配关键字,可选值:contains | starts-with | ends-with
  • 值域范围:between, notbetween,适用于日期、时间、数值类型,matchValues存放2个字符串值且后者大于前者
  • 枚举值:in, notin
  • 空非空:isnull, notnull
  • 其他:缺省类型值为eq,对于日期建议使用Long型表示 、支持这些格式 {number}d, {number}h, {number}m, {number}s, {number}, {yyyy-MM-dd}, {yyyy-MM-dd HH:mm:ss}

主数据管理平台中数据的5种状态

  • 0:生效(有效)
  • 1:停用(禁用)
  • 2:作废(废弃)
  • 3:待审批(审核中)
  • 4:历史
  • 5:编辑(录入)

数据状态支持的4种匹配方式

使用等于eq)进行匹配,如:

{
  "name": "M_DATA_STATE", 
  "matchValue": "0"
}

使用不等于ne)进行匹配,如:

{
  "matchType": "ne",
  "matchValue": "0",
  "name": "M_DATA_STATE"
}

使用在枚举值内in)进行匹配,如:

{
  "matchType": "in",
  "matchValues": [ "0", "1", "2" ],
  "name": "M_DATA_STATE"
}

使用不在枚举值内notin)进行匹配,如:

{
  "matchType": "notin",
  "matchValues": [ "0", "3", "5" ],
  "name": "M_DATA_STATE"
}

其他查询参数

  • execCountSql

    execCountSql=false, 一般情况下在查询条件不变的条件下,第n+1次查询时可以不需要后端返回记录总数,可以传递此参数节省一次SQL查询(节约一点点时间)
    或是在使用如字段主键之类的条件查询,结果集是可预期内的数据。此参数不影响数据查询结果。
    
  • 非法参数处理策略

    查询条件非法参数处理策略,默认忽略(IGNORE)可以传递查询参数修改为 illegalArgStrategy=ERROR  
    
  • 继承模型:

    includeDescendants=true 
    查询包括子孙模型的数据(多级子)
    
    includeChildren=true 
    查询包括子模型的数据(一级子)
    

引用组合模型的字段作为查询条件

匹配方式可以使用 in eq,值为字段其引用的组合模型的数据M_ID集合或单值。参考示例:

{
    "args": [{
        "name": "M_DATA_STATE",
        "matchValue": "0"
    }, {
        "name": "work_experience",
        "matchType": "in",
        "matchValues": [ "33c8dddb-3f0a-4d4f-a6e8-b61ae6ffcf30", "218e9c59-89b5-4564-ab7f-2d1a11830a65", "f8ba92a7-ef96-4d53-bc48-033c52503f2b" ]
    }],
    "parseRef": {
        "parseRefReferenceField": true
    }
}

补充说明

MDM后端服务拼装转化后的SQL条件格式:

WHERE ( 1.行权限SQL条件 )  AND  ( 2.系统字段参数#args ) AND ( 3.用户查询条件#args ) AND ( 4.复杂嵌套查询条件#criteria )  AND ( 5.模型额外条件 )
1. 支持复杂的嵌套结构,在模型管理角色授权界面上进行配置;
2. 系统内置字段,模型的数据维护界面上的数据状态查询框即系统字段 `M_DATA_STATE`
3. 用户查询条件#args,根据用户需要,使用模型的合法字段作为查询条件,多条件之间的逻辑关系默认AND,支持传参设置为OR;
4. 复杂嵌套查询条件#criteria,可与args混用;
5. 模型扩展条件,目前只有继承模型会额外传递可选查询参数(二选一),`includeDescendants=true` 查询包括子孙模型的数据,`includeChildren=true` 查询包括子模型的数据,MDM后端转化并拼装后的SQL为 `M_MODEL_ID = ?` 或 `M_MODEL_ID in (?, ?, ?, ...)`

数据查询接口 — GET方式

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/data-ops/{dataModelCode}/query
  • 请求参数:
参数 位置 类型 描述
dataModelCode Path String 数据模型编码
withSysColumn Query Boolean 查询结果是否包含系统字段
pageNum Query Integer 分页页数
pageSize Query Integer 分页大小

GET方式查询就是把前面POST方式的Body参数分解成查询参数,下面介绍一下映射规则:

查询条件参数组装说明:
----------------------------------------------------
参数格式:p{index}={column-code}
- index 从0开始, Exp. p0=name p1=age
- index 必须连续,如果中断则后面的参数则被忽略

匹配值格式:p{index}MatchValue={values}
- 缩写格式:p{index}v={values}
- index与参数一一对应
- 多值使用逗号连接,between/in情况下使用

匹配方式格式:p{index}MatchType=eq
- 缩写格式:p{index}m=eq
- index与参数一一对应
- 缺省值'eq'

匹配可选属性设置:p{index}MatchOption=ends-with
- 缩写格式:p{index}o=ends-with
- index与参数一一对应
- 缺省值'contains'


​ 排序字段参数组装说明: ​ ---------------------------------------------------- ​ 排序字段格式:s{index}={column-code} ​ - index from 0, exp. p0=name p1=age ​ - index 必须连续,与查询参数的index无关 ​
排序方式格式:s{index}Asc={boolean}

- index与前者一一对应
- 缺省值'true'

转换引用字段:
----------------------------------------------------
引用字段查询转换、默认开启,可以通过以下方式关闭;
parseRefDictField=false
parseRefOrdinaryField=false
引用组合模型通过下面方式开启;
parseRefReferenceField=true

引用字段值替换默认关闭,新增一个`${field}__ref`字段存入实际值,可以通过以下方式开启;
replaceRefDictValue=true
replaceRefOrdinaryValue=true

缺省新字段后置为`__ref`,可以通过参数`parseRefWithSuffix`进行自定义;
parseRefWithSuffix=__ref

数据推送APIs

三方系统集成主数据管理平台时,如果通过webhook方式订阅主数据,则需要按下面的规范实现其中一种适配方式。

Http

接收主数据管理平台数据推送API

  • 方法(Method):POST
  • 路径(PATH):三方系统自定义,例如/api/mdm/hook
  • 请求参数:
参数 位置 类型 描述
model Query String 必填参数,数据模型编码
dict Query Boolean 可选参数,是否是字典,默认不是字典
data Body JSON 数据记录JSON格式(N条)
Content-Type Header String application/json

请求报文结构参考:(以M_开头为主数据管理平台的系统字段)

[
  {
    "M_CREATED_AT": 1615448026000,
    "M_CREATED_BY": "lizw",
    "M_CREATED_DEPT": "dev",
    "M_DATA_FROM": "mdm::manual",
    "M_DATA_STATE": 0,
    "M_DATA_UID": "c8796466-843b-43a2-9162-87436ced374f",
    "M_DATA_VERSION": 1,
    "M_ID": "c8f86050-5dd2-4c50-8d59-cb5b95d135d8",
    "M_LAST_MODIFIED_AT": 1615448051000,
    "M_LAST_MODIFIED_BY": "lizw",
    "M_MODEL_ID": "040bcc6d-5953-4e90-aa60-6ba481222250",
    "M_MODEL_VERSION": 1,
    "M_SECURITY_LEVEL": 1,
    "age": 33,
    "name": "张飞",
    "gender": "male",
    "gender__ref": "男",
    "courses": [{
      "course": "math",
      "scores": "99"
    }, {
      "course": "chinese",
      "scores": "60"
    }, {
      "course": "sunzibingfa",
      "scores": "A"
    }]
  },
  {
    "M_CREATED_AT": 1615448042000,
    "M_CREATED_BY": "lizw",
    "M_CREATED_DEPT": "dev",
    "M_DATA_FROM": "mdm::manual",
    "M_DATA_STATE": 0,
    "M_DATA_UID": "3fcf8ef9-87be-4f4d-9296-442b8b48dad6",
    "M_DATA_VERSION": 1,
    "M_ID": "f1bc2298-05f5-41ec-9881-cfd5fdf8ca15",
    "M_LAST_MODIFIED_AT": 1615448051000,
    "M_LAST_MODIFIED_BY": "lizw",
    "M_MODEL_ID": "040bcc6d-5953-4e90-aa60-6ba481222250",
    "M_MODEL_VERSION": 1,
    "M_SECURITY_LEVEL": 1,
    "age": 36,
    "name": "关羽",
    "gender": "male",
    "gender__ref": "男",
    "courses": [{
      "course": "math",
      "scores": "99"
    }, {
      "course": "chinese",
      "scores": "98"
    }, {
      "course": "sunzibingfa",
      "scores": "A+"
    }]
  }
]

【说明】 推送的数据报文内容是JSON格式,包括系统字段(M_开头)和用户定义的模型字段(已发布),如果存在引用字段(引用字典或普通模型)可能会存在xxx__ref字段值, 用户可以根据需要使用或忽略(可以单独查询字典数据),如果存在引用组合模型的字段,则其值为一个对象数据,如上面例子中的课程courses (每一个元素的字段中也包含系统字段,因为系统字段较多示例中没添加)。对于http推送方式,在设置推送数据限制为1条情况下默认payloadJSON Array格式, 允许在订阅者请求头里配置扩展属性EXTRA_PAYLOAD_JSON_ARRAY: false 改成单条数据格式 [{...},{...}, ...] => {...}

字典推送报文参考:

[
    {
        "code": "004",
        "name": "阿富汗",
        "iso_number": "ISO 3166-2:AF",
        "en_name": "Afghanistan",
        "__event__": {
          "type": "ADD",
          "time": 1624603828956
        }
    },
    {
        "code": "010",
        "name": "南极洲",
        "iso_number": "ISO 3166-2:AQ",
        "en_name": "Antarctica",
        "__event__": {
          "type": "ADD",
          "time": 1624603828956
        }
    }
]

【说明】
__event__为事件相关数据,事件类型type有三个枚举值ADD MODIFIED DELETED

系统健康检查API

  • 方法(Method):GET
  • 路径(PATH):三方系统自定义,例如/api/mdm/ping
  • 返回状态码200即表示三方系统服务正常

WebService

接收主数据管理平台数据推送的WebService方法

  • 命名空间namespace, 接收推送的方法名method三方系统自定义,例如hook
  • 方法定义以下两个参数,三方系统根据推送内容实现自己的业务逻辑
参数 类型 描述
arg0 (model) String 数据模型编码
arg1 (message) String 推送数据记录JSON内容,参考Http方式一节中的报文格式
arg2 (dict) Boolean 是否是字典

Example:

package com.mdm.data.webhook;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class MdmDataModelWebhook {
    @WebMethod
    public String hook(String model, String message, boolean dict) {
        // TODO your work
        System.out.println("Receive a data model: " + model + " message:\n" + message);
        return "OK";
    }

    @WebMethod
    public String ping() {
        return "OK";
    }
}

系统健康检查方法

提供一个无参数的ws方法,命名空间与前者一致。

扩展推送实现

实现接口com.primeton.mdm.management.spi.MDMDataPushHandler 注册为 Spring Bean,把实现代码编译成JAR放入MDM程序${MDM_HOME}/lib/中即可。 如果需要覆写http, webservice实现,则调整实现类的优先级即可(覆写int getOrder(),数值越小优先级越大,默认值100) 如果扩展新的推送API类型实现,【我的账户头像】-> 【管理平台】 -> 【系统配置】 -> 【业务字典】 查找mdm-subscriber-adapter并添加类型,如kafka,这样再新增订阅者就可以选择这个新增的类型。

package com.primeton.mdm.management.spi;

import com.primeton.mdm.management.model.MDMSubscriber;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;

@Component
public class KafkaDataPushHandler implements MDMDataPushHandler {

  int getOrder() {
    return 100;
  }

  @Override
  public String getType() {
    return "kafka"; // http, webservice, ...
  }

  @Override
  public void push(MDMSubscriber subscriber, String modelCode, Page<Map<String, Object>> data, boolean dict) {
      String serverUrl = subscriber.getServerUrl();
      Properties extProps = subscriber.getExtProps();
      // e.g.
      String user = extProps.getProperty("user", "guest");
      String password = extProps.getProperty("password", "123456");
      // TODO
  }

}

MDM内置http推送实现类MDMHttpDataPushHandler代码参考:


@Component
public class MDMHttpDataPushHandler implements MDMDataPushHandler {

    public static final String TYPE = "http";

    private final RestTemplate restTemplate = new RestTemplateBuilder().build();

    @Autowired
    private MDMSubscriberHeartbeat heartbeat;

    @Override
    public String getType() {
        return TYPE;
    }

    /**
     * <code>RestTemplate</code> custom settings by override this method
     * @return RestTemplate
     */
    protected RestTemplate getRestTemplate(boolean ssl) {
        return restTemplate;
    }

    /**
     * throw <code>UnavailableException</code> if target service unavailable
     */
    @Override
    public void push(MDMSubscriber subscriber, String modelCode, Page<Map<String, Object>> data, boolean dict) {
        if (null == subscriber || StringUtils.isBlank(modelCode) || null == data || CollectionUtils.isEmpty(data.getContent())) {
            return;
        }

        if (StringUtils.isNotBlank(subscriber.getPingApi())) {
            HealthState healthState = heartbeat.getHealthState(subscriber);
            if (null != healthState && !healthState.isOk()) {
                throw new UnavailableException("Unavailable [" + subscriber.getSystemName() + "], " + healthState.getCause());
            }
        }

        String serverUrl = subscriber.getServerUrl();
        String uriPath = subscriber.getPushApi();
        uriPath = uriPath.charAt(0) == '/' ? uriPath : "/" + uriPath;
        int index = uriPath.indexOf("?");
        String extraQuery = index > 0 && index < uriPath.length() - 1 ? "&" + uriPath.substring(index + 1) : "";
        uriPath = index > 0 ? uriPath.substring(0, index) : uriPath;

        if (!Stream.of("http", "https").anyMatch(serverUrl.toLowerCase()::startsWith)) {
            serverUrl = "http://" + serverUrl; // default http
        }
        URI uri = URI.create(serverUrl);
        boolean ssl = "https".equalsIgnoreCase(uri.getScheme());
        String query = uri.getQuery();
        query = StringUtils.isBlank(query) ? "" : ("&" + query);

        String pushUrl = UrlUtils.buildFullRequestUrl(uri.getScheme(), uri.getHost(), uri.getPort() < 1 ? (ssl ? 443 : 80) : uri.getPort(), uriPath,
                (dict ? "dict=true&" : "") + "model=" + modelCode + query + extraQuery);

        HttpHeaders headers = new HttpHeaders();
        fillHttpHeaders(headers, subscriber.getExtProps());
        headers.set("X-MDM-RequestTime", String.valueOf(System.currentTimeMillis()));
        headers.set("X-MDM-RequestId", UUID.randomUUID().toString());
        {
            MediaType contentType = headers.getContentType();
            if (null == contentType || !contentType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
                headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
            }
            List<MediaType> acceptTypes = headers.getAccept();
            if (CollectionUtils.isEmpty(acceptTypes) || acceptTypes.stream().noneMatch(t -> MediaType.ALL == t)) {
                acceptTypes = null == acceptTypes ? new ArrayList<>() : new ArrayList<>(acceptTypes);
                acceptTypes.add(MediaType.ALL);
                headers.setAccept(acceptTypes);
            }
            if (CollectionUtils.isEmpty(headers.getAcceptLanguage())) {
                headers.setAcceptLanguageAsLocales(Arrays.asList(Locale.SIMPLIFIED_CHINESE, Locale.TRADITIONAL_CHINESE, Locale.CHINESE, Locale.US, Locale.UK, Locale.ENGLISH));
            }
        }
        boolean array = Optional.ofNullable(subscriber.getExtProps()).map(props -> props.getProperty("EXTRA_PAYLOAD_JSON_ARRAY", "true")).map(Boolean::parseBoolean).orElse(Boolean.TRUE);
        HttpEntity<?> payload = data.getContent().size() == 1 && !array ? new HttpEntity<>(data.getContent().get(0), headers) : new HttpEntity<>(data.getContent(), headers);

        ResponseEntity<byte[]> response = null;
        try {
            response = getRestTemplate(ssl).exchange(pushUrl, HttpMethod.POST, payload, byte[].class);
        } catch (RestClientException e) {
            String message = e.getMessage();
            if (e instanceof ResourceAccessException || StringUtils.contains(message, "ConnectException") || StringUtils.contains(message, "Connection refused")) {
                throw new UnavailableException("Connection refused, " + message);
            }
            if (e instanceof HttpServerErrorException.InternalServerError) {
                throw new RuntimeException(((HttpServerErrorException.InternalServerError) e).getResponseBodyAsString(), e); //NOSONAR
            }
            if (e instanceof HttpClientErrorException) {
                HttpClientErrorException ex = (HttpClientErrorException) e;
                throw new UnavailableException(ex.getStatusCode() + ", " + ex.getResponseBodyAsString());
            }
            throw new UnavailableException(message, e);
        }
        if (!response.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("<push> <" + response.getStatusCodeValue() + ">. Body payload: " + Optional.ofNullable(getResponseBody(response)).orElse("<none>"));  //NOSONAR
        }
    }

    private String getResponseBody(ResponseEntity<byte[]> response) {
        if (!response.hasBody()) {
            return null;
        }
        if (ArrayUtils.isEmpty(response.getBody())) {
            return "";
        }
        try {
            List<Charset> charsets = response.getHeaders().getAcceptCharset();
            Charset charset = CollectionUtils.isEmpty(charsets) ? Charset.forName("UTF-8") : charsets.get(0);
            return new String(response.getBody(), charset);
        } catch (Exception e) {
            return null;
        }
    }

    protected void fillHttpHeaders(HttpHeaders httpHeaders, Properties extProps) {
        if (null == extProps || extProps.isEmpty()) {
            return;
        }
        String headers = extProps.getProperty("headers");
        if (StringUtils.isBlank(headers)) {
            // 使用第二种写法
            extProps.forEach((key, value) -> {
                String header = key.toString().trim();
                if (!header.startsWith("EXTRA_")) {
                    httpHeaders.set(header, value.toString());
                }
            });
            return;
        }
        // 兼容7.0LA的存储和解析方式
        Stream.of(headers.split(" ; ")).map(String::trim).filter(e -> e.length() > 0 && e.contains(":")).forEach(e -> {
            int index = e.indexOf(":");
            if (index > 1 && index < e.length() - 1) {
                httpHeaders.set(e.substring(0, index).trim(), e.substring(index + 1).trim());
            }
        });
    }

    @Override
    public HealthState ping(MDMSubscriber subscriber) {
        String uriPath = subscriber.getPingApi();
        if (StringUtils.isBlank(uriPath)) {
            return null;
        }
        String serverUrl = subscriber.getServerUrl();
        uriPath = uriPath.charAt(0) == '/' ? uriPath : "/" + uriPath;
        if (!Stream.of("http", "https").anyMatch(serverUrl::startsWith)) {
            serverUrl = "http://" + serverUrl; // default http
        }
        URI uri = URI.create(serverUrl);
        boolean ssl = "https".equalsIgnoreCase(uri.getScheme());

        String pingUrl = UrlUtils.buildFullRequestUrl(uri.getScheme(), uri.getHost(), uri.getPort() < 1 ? (ssl ? 443 : 80) : uri.getPort(), uriPath, uri.getQuery());
        HttpHeaders headers = new HttpHeaders();
        headers.set("X-MDM-RequestTime", String.valueOf(System.currentTimeMillis()));
        headers.set("X-MDM-RequestId", UUID.randomUUID().toString());
        fillHttpHeaders(headers, subscriber.getExtProps());
        {
            List<MediaType> acceptTypes = headers.getAccept();
            if (CollectionUtils.isEmpty(acceptTypes) || acceptTypes.stream().noneMatch(t -> MediaType.ALL == t)) {
                acceptTypes = null == acceptTypes ? new ArrayList<>() : new ArrayList<>(acceptTypes);
                acceptTypes.add(MediaType.ALL);
                headers.setAccept(acceptTypes);
            }
            if (CollectionUtils.isEmpty(headers.getAcceptLanguage())) {
                headers.setAcceptLanguageAsLocales(Arrays.asList(Locale.SIMPLIFIED_CHINESE, Locale.TRADITIONAL_CHINESE, Locale.CHINESE, Locale.US, Locale.UK, Locale.ENGLISH));
            }
        }

        HealthState state = new HealthState();
        state.setTimestamp(System.currentTimeMillis());
        try {
            HttpEntity<String> payload = new HttpEntity<>(null, headers);
            ResponseEntity<byte[]> response = getRestTemplate(ssl).exchange(pingUrl, HttpMethod.GET, payload, byte[].class);
            state.setOk(response.getStatusCode().is2xxSuccessful());
            state.setCause(state.isOk() ? response.getStatusCode().toString() : getResponseBody(response));
        } catch (RestClientException e) {
            String cause = (e instanceof HttpServerErrorException.InternalServerError)
                    ? ((HttpServerErrorException.InternalServerError) e).getResponseBodyAsString() : e.getMessage();
            if (e instanceof HttpClientErrorException) {
                HttpClientErrorException ex = (HttpClientErrorException) e;
                cause = ex.getStatusCode() + ", " + ex.getResponseBodyAsString();
            }
            state.setCause(cause);
        }
        return state;
    }

}

数据字典分类APIs

新增

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/dict-types
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Body String 分类编码
id Body String 分类id
isLeaf Body Boolean 是否叶子类型
name Body String 分类名称
orderValue Body Integer 排序号
parentId Body String 父分类id
  • 请求报文示例:
{
  "code": "string",
  "id": "string",
  "isLeaf": true,
  "name": "string",
  "orderValue": 0,
  "parentId": "string"
}
  • 响应报文示例:
{
  "code": "string",
  "id": "string",
  "isLeaf": true,
  "name": "string",
  "orderValue": 0,
  "parentId": "string",
  "treePath": "string"
}

更新

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-types
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Body String 分类编码
id Body String 分类id
isLeaf Body Boolean 是否叶子类型
name Body String 分类名称
orderValue Body Integer 排序号
parentId Body String 父分类id
  • 请求报文示例:
{
  "code": "string",
  "id": "string",
  "isLeaf": true,
  "name": "string",
  "orderValue": 0,
  "parentId": "string"
}
  • 响应报文示例:
{
  "code": "string",
  "id": "string",
  "isLeaf": true,
  "name": "string",
  "orderValue": 0,
  "parentId": "string",
  "treePath": "string"
}

按主键查询

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-types/{id}
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 字典分类ID

  • 响应报文示例:

{
  "code": "string",
  "id": "string",
  "isLeaf": true,
  "name": "string",
  "orderValue": 0,
  "parentId": "string",
  "treePath": "string"
}

按主键删除

  • 方法(Method):DELETE
  • 路径(PATH):/api/mdm/dict-types/{id}
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 字典分类ID

删除前检查

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-types/{id}/check-before-delete
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 字典分类ID

  • 响应报文示例:

{
  "isOk": true,
  "message": "string"
}

移动节点

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-types/{id}/move-to/{toParentId}
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 字典分类ID toParentId | Path | String | 目标父字典分类ID

按主键集合删除

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-types/batch-delete
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json ids | Body | String[] | 字典分类ID数组

批量删除前检查

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-types/{id}/check-before-delete
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json ids | Body | String[] | 字典分类ID数组

  • 响应报文示例:

{
  "id1": {
    "isOk": true,
    "message": "string"
  },
  "id2": {
    "isOk": true,
    "message": "string"
  },
  "id3": {
    "isOk": true,
    "message": "string"
  }
}

按条件计数

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-types/count
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json code | Query | String | 字典分类编码 id | Query | String | 字典分类id keyword | Query | String | 关键字 name | Query | String | 字典分类名称 parentId | Query | String | 父分类id parentIdIsNull | Query | Boolean | 是否有父分类 treePath | Query | String | 节点在树中的路径表示, 如: PARENT_TREE_PATH+ID+.

按条件分页查询

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-types/page-query
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json code | Query | String | 字典分类编码 id | Query | String | 字典分类id keyword | Query | String | 关键字 name | Query | String | 字典分类名称 pageNum | Query | Integer | 分页页数 pageSize | Query | Integer | 分页大小 parentId | Query | String | 父分类id parentIdIsNull | Query | Boolean | 是否有父分类 treePath | Query | String | 节点在树中的路径表示, 如: PARENT_TREE_PATH+ID+.

  • 响应报文示例:

{
  "content": [
    {
      "code": "string",
      "id": "string",
      "isLeaf": true,
      "name": "string",
      "orderValue": 0,
      "parentId": "string",
      "treePath": "string"
    }
  ],
  "empty": true,
  "first": true,
  "last": true,
  "number": 0,
  "numberOfElements": 0,
  "pageable": {
    "offset": 0,
    "pageNumber": 0,
    "pageSize": 0,
    "paged": true,
    "sort": {
      "empty": true,
      "sorted": true,
      "unsorted": true
    },
    "unpaged": true
  },
  "size": 0,
  "sort": {
    "empty": true,
    "sorted": true,
    "unsorted": true
  },
  "totalElements": 0,
  "totalPages": 0
}

按条件查询

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-types/query
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json code | Query | String | 字典分类编码 id | Query | String | 字典分类id keyword | Query | String | 关键字 name | Query | String | 字典分类名称 parentId | Query | String | 父分类id parentIdIsNull | Query | Boolean | 是否有父分类 treePath | Query | String | 节点在树中的路径表示, 如: PARENT_TREE_PATH+ID+.

  • 响应报文示例:

[
  {
    "code": "string",
    "id": "string",
    "isLeaf": true,
    "name": "string",
    "orderValue": 0,
    "parentId": "string",
    "treePath": "string"
  }
]

数据字典模型APIs

新增

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/dict-models
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Body String 模型编码
codeFieldCode Body String 编码字段CODE
dictTypeId Body String 字典分类id
fields Body Map[] 字段数组
id Body String 模型id
isFieldModified Body Boolean 是否修改字段
isPublished Body Boolean 是否发布
name Body String 模型名称
nameFieldCode Body String 显示字段CODE
orderFieldCode Body String 排序字段CODE
orderValue Body Integer 排序号
parentFieldCode Body String 树型字典的父字段CODE
tableName Body String 表名
type Body String 字典类型;LIST:列表, TREE:树形
  • 请求报文示例:
{
  "code": "string",
  "codeFieldCode": "string",
  "dictTypeId": "string",
  "fields": [
    {
      "code": "string",
      "dictModelId": "string",
      "id": "string",
      "length": 0,
      "name": "string",
      "orderValue": 0,
      "scale": 0,
      "state": "ADDED",
      "type": "VARCHAR"
    }
  ],
  "id": "string",
  "isFieldModified": true,
  "isPublished": true,
  "name": "string",
  "nameFieldCode": "string",
  "orderFieldCode": "string",
  "orderValue": 0,
  "parentFieldCode": "string",
  "tableName": "string",
  "type": "LIST"
}
  • 响应报文示例:
{
  "code": "string",
  "codeFieldCode": "string",
  "dictTypeId": "string",
  "fields": [
    {
      "code": "string",
      "dictModelId": "string",
      "id": "string",
      "length": 0,
      "name": "string",
      "orderValue": 0,
      "scale": 0,
      "state": "ADDED",
      "type": "VARCHAR"
    }
  ],
  "id": "string",
  "isFieldModified": true,
  "isPublished": true,
  "name": "string",
  "nameFieldCode": "string",
  "orderFieldCode": "string",
  "orderValue": 0,
  "parentFieldCode": "string",
  "tableName": "string",
  "type": "LIST"
}

更新

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-models
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Body String 模型编码
codeFieldCode Body String 编码字段CODE
dictTypeId Body String 字典分类id
fields Body Map[] 字段数组
id Body String 模型id
isFieldModified Body Boolean 是否修改字段
isPublished Body Boolean 是否发布
name Body String 模型名称
nameFieldCode Body String 显示字段CODE
orderFieldCode Body String 排序字段CODE
orderValue Body Integer 排序号
parentFieldCode Body String 树型字典的父字段CODE
tableName Body String 表名
type Body String 字典类型;LIST:列表, TREE:树形
  • 请求报文示例:
{
  "code": "string",
  "codeFieldCode": "string",
  "dictTypeId": "string",
  "fields": [
    {
      "code": "string",
      "dictModelId": "string",
      "id": "string",
      "length": 0,
      "name": "string",
      "orderValue": 0,
      "scale": 0,
      "state": "ADDED",
      "type": "VARCHAR"
    }
  ],
  "id": "string",
  "isFieldModified": true,
  "isPublished": true,
  "name": "string",
  "nameFieldCode": "string",
  "orderFieldCode": "string",
  "orderValue": 0,
  "parentFieldCode": "string",
  "tableName": "string",
  "type": "LIST"
}
  • 响应报文示例:
{
  "code": "string",
  "codeFieldCode": "string",
  "dictTypeId": "string",
  "fields": [
    {
      "code": "string",
      "dictModelId": "string",
      "id": "string",
      "length": 0,
      "name": "string",
      "orderValue": 0,
      "scale": 0,
      "state": "ADDED",
      "type": "VARCHAR"
    }
  ],
  "id": "string",
  "isFieldModified": true,
  "isPublished": true,
  "name": "string",
  "nameFieldCode": "string",
  "orderFieldCode": "string",
  "orderValue": 0,
  "parentFieldCode": "string",
  "tableName": "string",
  "type": "LIST"
}

按主键查询

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-models/{id}
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 模型ID

  • 响应报文示例:

{
  "code": "string",
  "codeFieldCode": "string",
  "dictTypeId": "string",
  "fields": [
    {
      "code": "string",
      "dictModelId": "string",
      "id": "string",
      "length": 0,
      "name": "string",
      "orderValue": 0,
      "scale": 0,
      "state": "ADDED",
      "type": "VARCHAR"
    }
  ],
  "id": "string",
  "isFieldModified": true,
  "isPublished": true,
  "name": "string",
  "nameFieldCode": "string",
  "orderFieldCode": "string",
  "orderValue": 0,
  "parentFieldCode": "string",
  "tableName": "string",
  "type": "LIST"
}

按主键删除

  • 方法(Method):DELETE
  • 路径(PATH):/api/mdm/dict-models/{id}
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 模型ID

删除前检查

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-models/{id}/check-before-delete
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 模型ID

  • 响应报文示例:

{
  "isOk": true,
  "message": "string"
}

发布模型

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-models/{id}/publish
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 模型ID

取消发布模型

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-models/{id}/unpublish
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json id | Path | String | 模型ID

按主键集合删除

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-models/batch-delete
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json ids | Body | String[] | 模型id集合

  • 请求报文示例:

[
  "string"
]

批量删除前检查

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-models/check-before-batch-delete
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json ids | Body | String[] | 模型id集合

  • 请求报文示例:

[
  "id1", "id2", "id3"
]
  • 响应报文示例:
{
  "id1": {
    "isOk": true,
    "message": "string"
  },
  "id2": {
    "isOk": true,
    "message": "string"
  },
  "id3": {
    "isOk": true,
    "message": "string"
  }
}

按条件计数

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-models/count
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json code | Query | String | 模型编码 dictTypeId | Query | String | 字典分类id id | Query | String | 模型id name | Query | String | 模型名称

按条件分页查询

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-models/page-query
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json code | Query | String | 模型编码 dictTypeId | Query | String | 字典分类id id | Query | String | 模型id name | Query | String | 模型名称 pageNum | Query | Integer | 分页页数 pageSize | Query | Integer | 分页大小

  • 响应报文示例:

{
  "content": [
    {
      "code": "string",
      "codeFieldCode": "string",
      "dictTypeId": "string",
      "fields": [
        {
          "code": "string",
          "dictModelId": "string",
          "id": "string",
          "length": 0,
          "name": "string",
          "orderValue": 0,
          "scale": 0,
          "state": "ADDED",
          "type": "VARCHAR"
        }
      ],
      "id": "string",
      "isFieldModified": true,
      "isPublished": true,
      "name": "string",
      "nameFieldCode": "string",
      "orderFieldCode": "string",
      "orderValue": 0,
      "parentFieldCode": "string",
      "tableName": "string",
      "type": "LIST"
    }
  ],
  "empty": true,
  "first": true,
  "last": true,
  "number": 0,
  "numberOfElements": 0,
  "pageable": {
    "offset": 0,
    "pageNumber": 0,
    "pageSize": 0,
    "paged": true,
    "sort": {
      "empty": true,
      "sorted": true,
      "unsorted": true
    },
    "unpaged": true
  },
  "size": 0,
  "sort": {
    "empty": true,
    "sorted": true,
    "unsorted": true
  },
  "totalElements": 0,
  "totalPages": 0
}

按条件查询

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-models/query
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json code | Query | String | 模型编码 dictTypeId | Query | String | 字典分类id id | Query | String | 模型id name | Query | String | 模型名称

  • 响应报文示例:

[
  {
    "code": "string",
    "codeFieldCode": "string",
    "dictTypeId": "string",
    "fields": [
      {
        "code": "string",
        "dictModelId": "string",
        "id": "string",
        "length": 0,
        "name": "string",
        "orderValue": 0,
        "scale": 0,
        "state": "ADDED",
        "type": "VARCHAR"
      }
    ],
    "id": "string",
    "isFieldModified": true,
    "isPublished": true,
    "name": "string",
    "nameFieldCode": "string",
    "orderFieldCode": "string",
    "orderValue": 0,
    "parentFieldCode": "string",
    "tableName": "string",
    "type": "LIST"
  }
]

查询带有父字典分类的字典模型

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-models/query-by-dict-name
  • 请求参数:

    参数 | 位置 | 类型 | 描述 ----|------|--------|--------- Content-Type | Header | String | application/json dictName | Query | String | 字典名称

数据字典字段APIs

新增

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/dict-fields
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Body String 字段编码
dictModelId Body String 字典模型id
id Body String 字段id
length Body Integer 字段长度
name Body String 字段名称
orderValue Body Integer 排序号
scale Body Integer 字段小数位长度
state Body String 字段状态;ADDED, MODIFIED, PUBLISHED
type Body String 字段类型;VARCHAR, INT, DATE, DATETIME
  • 请求报文示例:
{
  "code": "string",
  "dictModelId": "string",
  "id": "string",
  "length": 0,
  "name": "string",
  "orderValue": 0,
  "scale": 0,
  "state": "ADDED",
  "type": "VARCHAR"
}
  • 响应报文示例:
{
  "code": "string",
  "dictModelId": "string",
  "id": "string",
  "length": 0,
  "name": "string",
  "orderValue": 0,
  "scale": 0,
  "state": "ADDED",
  "type": "VARCHAR"
}

更新

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-fields
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Body String 字段编码
dictModelId Body String 字典模型id
id Body String 字段id
length Body Integer 字段长度
name Body String 字段名称
orderValue Body Integer 排序号
scale Body Integer 字段小数位长度
state Body String 字段状态;ADDED, MODIFIED, PUBLISHED
type Body String 字段类型;VARCHAR, INT, DATE, DATETIME
  • 请求报文示例:
{
  "code": "string",
  "dictModelId": "string",
  "id": "string",
  "length": 0,
  "name": "string",
  "orderValue": 0,
  "scale": 0,
  "state": "ADDED",
  "type": "VARCHAR"
}
  • 响应报文示例:
{
  "code": "string",
  "dictModelId": "string",
  "id": "string",
  "length": 0,
  "name": "string",
  "orderValue": 0,
  "scale": 0,
  "state": "ADDED",
  "type": "VARCHAR"
}

按主键查询

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-fields/{id}
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
id Path String 字段id
  • 响应报文示例:
{
  "code": "string",
  "dictModelId": "string",
  "id": "string",
  "length": 0,
  "name": "string",
  "orderValue": 0,
  "scale": 0,
  "state": "ADDED",
  "type": "VARCHAR"
}

按主键删除

  • 方法(Method):DELETE
  • 路径(PATH):/api/mdm/dict-fields/{id}
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
id Path String 字段id

删除前检查

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-fields/{id}/check-before-delete
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
id Path String 字段id
  • 响应报文示例:
{
  "isOk": true,
  "message": "string"
}

按主键集合删除

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-fields/batch-delete
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
ids Body String[] 字段id集合
  • 请求报文示例:
[
  "string"
]

批量删除前检查

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-fields/check-before-batch-delete
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
ids Body String[] 字段id集合
  • 请求报文示例:
[
  "string"
]
  • 响应报文示例:
{
  "additionalProp1": {
    "isOk": true,
    "message": "string"
  },
  "additionalProp2": {
    "isOk": true,
    "message": "string"
  },
  "additionalProp3": {
    "isOk": true,
    "message": "string"
  }
}

按条件计数

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-fields/count
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Query String 字段编码
dictModelId Query String 字典模型id
id Query String 字段id

按条件分页查询

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-fields/page-query
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Query String 字段编码
dictModelId Query String 字典模型id
id Query String 字段id
pageNum Query Integer 分页页数
pageSize Query Integer 分页大小
  • 响应报文示例:
{
  "content": [
    {
      "code": "string",
      "dictModelId": "string",
      "id": "string",
      "length": 0,
      "name": "string",
      "orderValue": 0,
      "scale": 0,
      "state": "ADDED",
      "type": "VARCHAR"
    }
  ],
  "empty": true,
  "first": true,
  "last": true,
  "number": 0,
  "numberOfElements": 0,
  "pageable": {
    "offset": 0,
    "pageNumber": 0,
    "pageSize": 0,
    "paged": true,
    "sort": {
      "empty": true,
      "sorted": true,
      "unsorted": true
    },
    "unpaged": true
  },
  "size": 0,
  "sort": {
    "empty": true,
    "sorted": true,
    "unsorted": true
  },
  "totalElements": 0,
  "totalPages": 0
}

按条件查询

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-fields/query
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
code Query String 字段编码
dictModelId Query String 字典模型id
id Query String 字段id
  • 响应报文示例:
[
  {
    "code": "string",
    "dictModelId": "string",
    "id": "string",
    "length": 0,
    "name": "string",
    "orderValue": 0,
    "scale": 0,
    "state": "ADDED",
    "type": "VARCHAR"
  }
]

数据字典数据APIs

根据字典模型id分页查询字典模型数据

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-data/page-query
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
dictModelCode Query String 字典模型编码(或者使用dictModelId
dictModelId Query String 字典模型id(或者使用dictModelCode
pageNum Query Integer 分页页数
pageSize Query Integer 分页大小
column Query String 字典合法列名称
value Query String 字典合法列值
keyword Query String 模糊匹配关键字
  • 请求报文示例:
{
  "content": [
    {}
  ],
  "empty": true,
  "first": true,
  "last": true,
  "number": 0,
  "numberOfElements": 0,
  "pageable": {
    "offset": 0,
    "pageNumber": 0,
    "pageSize": 0,
    "paged": true,
    "sort": {
      "empty": true,
      "sorted": true,
      "unsorted": true
    },
    "unpaged": true
  },
  "size": 0,
  "sort": {
    "empty": true,
    "sorted": true,
    "unsorted": true
  },
  "totalElements": 0,
  "totalPages": 0
}

保存字典模型数据

  • 方法(Method):PUT
  • 路径(PATH):/api/mdm/dict-data
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
dictData Body Map[] 字典数据数组
dictModelId Query String 字典模型id

删除字典模型数据

  • 方法(Method):DELETE
  • 路径(PATH):/api/mdm/dict-data
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
dictDataCodes Query String[] 字典数据编码(逗号分隔)
dictModelId Query String 字典模型id

批量删除字典模型数据

  • 方法(Method):DELETE
  • 路径(PATH):/api/mdm/dict-data/bulk-delete
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
dictDataCodes Body String[] 字典数据编码数组(逗号分隔)
dictModelId Query String 字典模型id

导出字典数据

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-data/export-data
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
dictModelId Query String 字典模型id

导入字典数据

  • 方法(Method):POST
  • 路径(PATH):/api/mdm/dict-data/import-data
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
dictModelId Query String 字典模型id
uploadFile FormData File 上传文件
  • 响应报文示例:
{
  "isOk": true,
  "message": "string"
}

获取导入字典数据模板

  • 方法(Method):GET
  • 路径(PATH):/api/mdm/dict-data/import-templates
  • 请求参数:
参数 位置 类型 描述
Content-Type Header String application/json
dictModelId Query String 字典模型id

字段编码功能扩展

MDM主数据平台支持客户通过编写接口com.primeton.mdm.management.spi.MDMExtensionEncoder实现类来扩展自己的编码规则。扩展步骤:

  • 管理员登录MDM控制台,【我的账户头像】-> 【管理平台】 -> 【系统配置】 -> 【业务字典】 查找mdm-encoder并添加类型,其编码值作为接口实现类getType方法的返回值;
  • 编写接口com.primeton.mdm.management.spi.MDMExtensionEncoder实现类,根据需要实现无状态有状态的接口生成编码方法,如果实现需要参数也可以在实现类中进行描述;
  • 把写好的实现自动注册为Spring Bean、最后编译成JAR文件,放入MDM后端程序的lib/目录下,重启MDM后端程序即可

下面给出几个扩展编码的示例:e.g.

示例1:

package com.primeton.mdm.management.spi.eg;

import com.primeton.mdm.management.spi.MDMExtensionEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.stereotype.Component;

/**
 * 无状态的日期时间编码
 */
@Component
public class DateTimeEncoder implements MDMExtensionEncoder {

    private List<ConfigArg> args;

    @Override
    public int getOrder() {
        return 1000; // 值越小优先级越高,对于相同的`getType`,用于覆写某些扩展实现
    }

    @Override
    public String getType() {
        return "datetime"; // 一般是唯一的名称、`afcenter`业务字典`mdm-encoder`的枚举值
    }

    @Override
    public boolean stateless() {
        return true; // 无状态的,根据有无状态系统调用不同的`generate`方法
    }

    @Override
    public String generate(int total, int index, Map<String, Object> config, List<String> snippets, Map<String, Object> data) {
        // total: 编码规则引用的码段总个数
        // index: 在编码规则中属于第几个码段,从0开始
        // config: 扩展编码规则定义的参数值
        // snippets: 前index个码段生成的编码片段
        // data: 待入库的那条数据
        String pattern = (String) config.getOrDefault("dateFormatPattern", "yyyyMMddHHmmss"); // dateFormatPattern 用户自定的参数,在`getArgs`中描述它的定义
        return DateFormatUtils.format(new Date(), pattern);
    }

    @Override
    public List<ConfigArg> getArgs() {
        if (null != args) {
            return args;
        }
        args = new ArrayList<>();
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.STRING);
            arg.setLabel("日期格式");
            arg.setName("dateFormatPattern");
            arg.setRequired(true);
            arg.setSummary("用于格式化当前时间输出");
            arg.setSelectValues(patterns());
            args.add(arg);
        }
        return args;
    }

    private List<Map<String, String>> patterns() {
        Date now = new Date();
        List<Map<String, String>> formats = Stream.of("yyyy", "yyyyMM", "yyyyMMdd", "yyyyMMddHH", "yyyyMMddHHmm", "yyyyMMddHHmmss",
                "yy", "yyMM",  "yyMMdd", "yyMMddHH", "yyMMddHHmm", "yyMMddHHmmss",
                "HH", "HHmm", "HHmmss").map(p -> {
            Map<String, String> kv = new HashMap<>();
            kv.put("label", p + " (" + DateFormatUtils.format(now, p) + ")");
            kv.put("value", p);
            return kv;
        }).collect(Collectors.toList());
        return formats;
    }

}

示例2:

package com.primeton.mdm.management.spi.eg;

import com.primeton.mdm.management.spi.MDMExtensionEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;

/**
 * 自增序号
 */
@Component
public class SequenceEncoder implements MDMExtensionEncoder {

    private List<ConfigArg> args;

    @Override
    public int getOrder() {
        return 1000;
    }

    @Override
    public String getType() {
        return "sequence";
    }

    @Override
    public boolean stateless() {
        return false; // 有状态的编码实现
    }

    @Override
    public String generate(int total, int index, Map<String, Object> config, Map<String, Object> state, List<String> snippets, Map<String, Object> data) {
        // state: 存取用户定义的状态数据
        int length = ((Number) config.getOrDefault("length", 5)).intValue();
        long start = ((Number) config.getOrDefault("start", 0)).longValue();
        boolean fixed = (Boolean) config.getOrDefault("fixed", Boolean.TRUE); // 定长模式

        long current = ((Number) state.getOrDefault("current", -1L)).longValue();
        current = current == -1L ? start : (current + 1);
        state.put("current", current); // 更新状态
        if (!fixed) { // 非定长编码
            return String.valueOf(current);
        }
        // 定长编码
        StringBuilder sb = new StringBuilder().append(current);
        if (sb.length() > length) {
            throw new RuntimeException("Exceed length limit");
        }
        while (sb.length() < length) {
            sb.insert(0, 0);
        }
        return sb.toString();
    }

    @Override
    public List<ConfigArg> getArgs() {
        if (null != args) {
            return args;
        }
        args = new ArrayList<>();
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.NUMBER);
            arg.setLabel("编码长度");
            arg.setName("length");
            arg.setRequired(true);
            arg.setDefValue(6);
            arg.setSummary("生成编码总长度限制。");
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.NUMBER);
            arg.setLabel("起始值");
            arg.setName("start");
            arg.setRequired(true);
            arg.setDefValue(1);
            arg.setSummary("编码起始值。");
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.BOOLEAN);
            arg.setLabel("定长模式");
            arg.setName("fixed");
            arg.setDefValue(Boolean.TRUE);
            arg.setSummary("定长模式,生成的编码不足长度则前面补0。");
            args.add(arg);
        }
        return args;
    }

}

示例3:

package com.primeton.mdm.management.spi.eg;

import com.primeton.mdm.management.spi.MDMExtensionEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.stereotype.Component;

/**
 * 日期流水码(支持每日重置流水号)
 */
@Component
public class DateSequenceEncoder implements MDMExtensionEncoder {

    private List<ConfigArg> args;

    @Override
    public int getOrder() {
        return 1000;
    }

    @Override
    public String getType() {
        return "date-sequence";
    }

    @Override
    public boolean stateless() {
        return false;
    }

    @Override
    public String generate(int total, int index, Map<String, Object> config, Map<String, Object> state, List<String> snippets, Map<String, Object> data) {
        String pattern = (String) config.getOrDefault("dateFormatPattern", "yyyyMMdd");
        int length = ((Number) config.getOrDefault("length", 5)).intValue();
        long start = ((Number) config.getOrDefault("start", 0)).longValue();
        boolean fixed = (Boolean) config.getOrDefault("fixed", Boolean.TRUE); // 定长模式
        boolean resetEveryDay = (Boolean) config.getOrDefault("resetEveryDay", Boolean.FALSE); // 每天重置序号

        String date = DateFormatUtils.format(new Date(), pattern);

        String lastDate = (String) state.get("lastDate");
        // TODO 如果日期`pattern`含有时分秒则这里进行DAY比较
        long current = null == lastDate || lastDate.equals(date) ? ((Number) state.getOrDefault("current", -1L)).longValue() : -1L;
        current = current == -1L ? start : (current + 1);

        state.put("current", current); // 更新状态
        if (resetEveryDay) {
            state.put("lastDate", date);
        }
        if (!fixed) { // 非定长编码
            return date + current;
        }
        // 定长编码
        StringBuilder sb = new StringBuilder().append(current);
        if (sb.length() > length) {
            throw new RuntimeException("Exceed length limit");
        }
        while (sb.length() < length) {
            sb.insert(0, 0);
        }
        sb.insert(0, date);
        return sb.toString();
    }

    @Override
    public List<ConfigArg> getArgs() {
        if (null != args) {
            return args;
        }
        args = new ArrayList<>();
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.STRING);
            arg.setLabel("日期格式");
            arg.setName("dateFormatPattern");
            arg.setRequired(true);
            arg.setSummary("用于格式化当前时间输出");
            arg.setSelectValues(patterns());
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.NUMBER);
            arg.setLabel("编码长度");
            arg.setName("length");
            arg.setRequired(true);
            arg.setDefValue(6);
            arg.setSummary("生成编码总长度限制。");
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.NUMBER);
            arg.setLabel("起始值");
            arg.setName("start");
            arg.setRequired(true);
            arg.setDefValue(1);
            arg.setSummary("编码起始值。");
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.BOOLEAN);
            arg.setLabel("定长模式");
            arg.setName("fixed");
            arg.setDefValue(Boolean.TRUE);
            arg.setSummary("定长模式,生成的编码不足长度则前面补0。");
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.BOOLEAN);
            arg.setLabel("每天重置");
            arg.setName("resetEveryDay");
            arg.setDefValue(Boolean.FALSE);
            arg.setSummary("每天重置流水号,从头开始编码。");
            args.add(arg);
        }
        return args;
    }

    private List<Map<String, String>> patterns() {
        Date now = new Date();
        List<Map<String, String>> formats = Stream.of("yyyyMMdd", "yyMMdd", "MMddyyyy", "MMddyy").map(p -> {
            Map<String, String> kv = new HashMap<>();
            kv.put("label", p + " (" + DateFormatUtils.format(now, p) + ")");
            kv.put("value", p);
            return kv;
        }).collect(Collectors.toList());
        return formats;
    }

}

示例4:

package com.primeton.mdm.management.spi.eg;

import com.primeton.mdm.management.spi.MDMExtensionEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.springframework.stereotype.Component;

@Component
public class UUIDEncoder implements MDMExtensionEncoder {

    private List<ConfigArg> args;

    @Override
    public String getType() {
        return "uuid";
    }

    @Override
    public int getOrder() {
        return 1000;
    }

    @Override
    public boolean stateless() {
        return true;
    }

    @Override
    public String generate(int total, int index, Map<String, Object> config, List<String> snippets, Map<String, Object> data) {
        int length = ((Number) config.getOrDefault("length", 32)).intValue();
        String prefix = Optional.ofNullable((String) config.get("prefix")).orElse("");
        String suffix = Optional.ofNullable((String) config.get("suffix")).orElse("");
        boolean upperCase = (Boolean) config.getOrDefault("upperCase", Boolean.FALSE); // 定长模式

        String uuid = UUID.randomUUID().toString().replace("-", "");
        uuid = upperCase ? uuid.toUpperCase() : uuid;
        uuid = (length >= 6 && length < 32) ? uuid.substring(0, length) : uuid;
        return prefix + uuid + suffix;
    }

    @Override
    public List<ConfigArg> getArgs() {
        if (null != args) {
            return args;
        }
        args = new ArrayList<>();
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.STRING);
            arg.setLabel("前缀");
            arg.setName("prefix");
            arg.setDefValue("");
            arg.setSummary("");
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.STRING);
            arg.setLabel("后缀");
            arg.setName("suffix");
            arg.setDefValue("");
            arg.setSummary("");
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.NUMBER);
            arg.setLabel("长度");
            arg.setName("length");
            arg.setRequired(true);
            arg.setDefValue(32);
            arg.setSummary("UUID长度,6-32。");
            args.add(arg);
        }
        {
            ConfigArg arg = new ConfigArg();
            arg.setType(Type.BOOLEAN);
            arg.setLabel("大写");
            arg.setName("upperCase");
            arg.setDefValue(Boolean.FALSE);
            arg.setSummary("UUID所有字母大写。");
            args.add(arg);
        }
        return args;
    }

}
  • 国际化编码表单

参数表单如果需要实现国际化,可以使用MDMRequestContext.current.getLocaleString()获取用户的语言信息。MDMExtensionEncoder#getArgs实现可以从国际化文件中读取JSON配置并转化成对象。 最简单的办法是改为继承MDM提供的国际化抽象编码器类com.primeton.mdm.management.spi.AbstractExtensionEncoder,只要把所需的参数配置为JSON文件即可(classpath:/mdm/encoder/${type}-${lang}.json)。 它也支持非国际化,覆写其保护方法protected boolean i18nEnabled()返回false即可(读取单一文件classpath:/mdm/encoder/${type}.json)。

国际化编码参数表单配置示例

package com.primeton.mdm.management.spi.eg;

import com.primeton.mdm.management.spi.AbstractExtensionEncoder;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;

@Component
public class SequenceEncoder extends AbstractExtensionEncoder {

    @Override
    public String getType() {
        return "sequence";
    }

    @Override
    public boolean stateless() {
        return false;
    }

    @Override
    public String generate(int total, int index, Map<String, Object> config, Map<String, Object> state, List<String> snippets, Map<String, Object> data) {
        int length = ((Number) config.getOrDefault("length", 5)).intValue();
        long start = ((Number) config.getOrDefault("start", 0)).longValue();
        boolean fixed = (Boolean) config.getOrDefault("fixed", Boolean.TRUE);
        // TODO
    }

}

编码器SequenceEncoder表单参数国际化(中文)配置文件:classpath:/mdm/encoder/sequence-zh.json

[
  {
    "name": "length",
    "type": "NUMBER",
    "label": "编码长度",
    "defValue": 6,
    "required": true,
    "summary": "生成编码总长度限制。"
  },
  {
    "name": "start",
    "type": "NUMBER",
    "label": "起始值",
    "defValue": 1,
    "required": true,
    "summary": "编码起始值。"
  },
  {
    "name": "fixed",
    "type": "BOOLEAN",
    "label": "定长模式",
    "defValue": true,
    "required": false,
    "summary": "定长模式,生成的编码不足长度则前面补0。"
  }
]

编码器SequenceEncoder表单参数国际化(英文)配置文件:classpath:/mdm/encoder/sequence-en.json

[
  {
    "name": "length",
    "type": "NUMBER",
    "label": "Sequence Length",
    "defValue": 6,
    "required": true,
    "summary": "Total length limit."
  },
  {
    "name": "start",
    "type": "NUMBER",
    "label": "Start Number",
    "defValue": 1,
    "required": true,
    "summary": "Start number."
  },
  {
    "name": "fixed",
    "type": "BOOLEAN",
    "label": "Fixed Length",
    "defValue": true,
    "required": false,
    "summary": "In fixed length mode, if the generated code is less than the length, 0 will be added in front."
  }
]

results matching ""

    No results matching ""