引言
适用范围
本文档适用于集成主数据管理平台的三方应用系统(供开发者参考),文档的适用产品版本为: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, notlike
,matchOption
缺省值contains
,matchValue
存放模糊匹配关键字,可选值: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条
情况下默认payload
是JSON 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."
}
]