# 客制化

本系统支持使用 Groovy (opens new window) 语言进行客制化开发, Groovy 语言是 Java 语言的一个超集, 其支持 Java 语言的语法,但增加了更多动态特性,更加适用于进行领域建模和运行时增强。

# 目标读者

本文档的目标读者为:本系统的开发和实施人员

# 动态字段

动态字段的定义过程包含两个步骤

  1. 创建动态字段定义,动态字段定义主要包含动态字段存储相关的信息,如数据类型、可选项等
  2. 创建动态字段实例,动态字段实例主要包含动态字段的显示相关的信息,如显示名称、显示控件类型等

系统支持在如下场景下创建动态字段实例以在前后端传递、保存数据:

  • 创建与某种 Domain Class 关联的动态字段,以保存对象实例的属性
  • 创建与某种 Domain Instance 关联的动态字段,以实现通过某个类型控制对象实例的属性列表
  • 创建与某个 Action 关联的动态字段,以实现在执行 Action 时,传递参数
  • 创建与某个 Wizard 关联的动态字段,以实现在执行 Wizard 时,传递参数

多个动态字段实例可以关联到同一个动态字段定义。

创建与 Domain Instance 关联的动态字段的详情请参考 Domain Instance 动态字段支持

# 创建动态字段

系统支持通过界面操作直接创建动态字段,具体操作步骤为:

  • 在界面上,使用开发人员帐号(默认系统为 developer)登陆系统,创建一个Dynamic Field Definition定义,示意如下:

Create Dynamic Field

  • 通过创建 Object Dynamic Field 对象,将新创建的动态字段与某对象类型关联,这样在创建、编辑该类型的对象时,系统即会显示该字段。

Create Object Dynamic Field

当前支持的动态字段及界面显示控件类型如下所述:

数据类型 描述 显示控件
STRING 字符 长文本输入框
单行字符输入框
多选输入框
单选输入框
单选或输入 未实现
多选或输入 未实现
Checkbox
Radio Button
DATETIME 日期+时间 日期+时间输入
DATE 日期 日期输入
INTEGER 整数 输入
多选输入框
单选输入框
单选或输入 未实现
多选或输入 未实现
Checkbox
Radio Button
DECIMAL 小数 输入框
多选输入框
单选输入框
单选或输入 未实现
多选或输入 未实现
Checkbox
Radio Button
百分比
货币
BOOLEAN 布尔 Switch
FILE 附件 单文件上传
多文件上传
IMAGE 图片 单图片上传及预览
多图片上传及预览
OBJECT 对象关联 关联到一个对象
关联到多个对象

系统限制

当前系统中,针对货币类型,显示的货币符号全部为 $,暂未实现多语言支持

动态字段也可以通过 CSV 导入 的方式进行创建

# 选择字段备选项设定

选择字段的备选项在创建 Dynamic Field Definition时,通过 options JSON字段进行设定, 设定后的所有备选项,会以 JSON 字符串的形式,保存在系统中

# 对象权限控制

对于无复杂逻辑,与操作对象数据无关,只与其类型有关的权限控制逻辑,可以使用界面配置方式。如果需要 根据对象中的值,如某些字段的值结合当前用户的角色来决定某用户是否可以操作某对象,则需要使用客制化 开发的方式实现。

对象的权限控制默认情况下,根据该对象创建的 API 端口是否暴露给某角色而设定, 该设定在 Request Map中设定, 以下的文档中,以对象a.b.DomainObject为例,说明针对该类型对象的相关权限配置

# 查看权限

如果要赋予某角色 ROLE_A 该对象的查看权限,则需要在 Request Map 中插入如下记录

HttpMethod, Config Attribute, Url
GET,"ROLE_A",/
GET,"ROLE_A",/DomainObject/**
GET,"ROLE_A",/domain/DomainObject
GET,"ROLE_A",/a_b_DomainObject/**
1
2
3
4
5

# 创建权限

如果需要将其创建权限赋予某角色 ROLE_A,则需要在 Request Map对象中, 创建如下的相关记录:

HttpMethod: POST
Config Attribute: ROLE_A
Url: /DomainObject
1
2
3

# 编辑权限

如果需要将其编辑权限赋予某角色 ROLE_A,则需要在 Request Map对象中, 创建如下的相关记录:

HttpMethod: PUT
Config Attribute: ROLE_A
Url: /DomainObject/**
1
2
3

# 删除权限

如果需要将其删除权限赋予某角色 ROLE_A,则需要在 Request Map对象中, 创建如下的相关记录:

HttpMethod: DELETE
Config Attribute: ROLE_A
Url: /DomainObject/**
1
2
3

# 客制化概述

动态逻辑使用 FieldHook, ObjectHook, DynamicAction, DynamicTask, DynamicFilter 等对象进行定义, 其具体执行的代码通过 Dynamic Logic对象进行定义,可以在界面上直接创建这些对象,并输入客制化逻辑代码, 创建这些对象的菜单路径为 Development > Logic and Task

# 类型概述

以下列出了系统中当前支持的客制化逻辑的类型及其使用场景

  • 动态权限
    • 对象的动态创建权限判断
    • 对象的动态删除和更新权限判断
  • 表单及字段客制化
    • 表单的字段联动、下拉选项,隐藏与显示等
    • 对象的快捷搜索搜索逻辑
    • 对象的弹出搜索窗的搜索逻辑
    • 字段的的默认值
    • 字段的校验
    • 表单中字段组的显示逻辑
    • 返回客户端的对象结构客制化逻辑
  • 对象生命周期客制化
    • 创建的客制化逻辑注入
    • 修改的客制化逻辑注入
    • 删除的的客制化逻辑注入
  • 动态动作
    • 动态动作的启用逻辑
    • 动态动作的核心逻辑
    • 外部命令动作的后处理逻辑
  • 定时任务
    • 定时任务的启用逻辑
    • 定时任务的核心逻辑
  • 仪表盘
    • 仪表盘的启用逻辑
    • 仪表盘组件的启用逻辑
    • 仪表盘组件的核心逻辑
    • 仪表盘组件的显示配置
  • 过滤器
    • 过滤器的启用逻辑
    • 过滤器的过滤条件配置

以下列出了 不同的 Hook Type 与 Dynamic Logic 的 Logic Type 的组合对应的不同的客制化类型

类型 Hook Type Logic Type 客制化类型
Object Hook Object create ability hook Object dynamic hook 创建权限
Object Hook Object update/delete ability hook 更新删除权限
Object Hook Object before creating addon hook 创建对象前
Object Hook Object after creating addon hook 创建对象后
Object Hook Object before deleting addon hook 删除对象前
Object Hook Object after deleting addon hook 删除对象后
Object Hook Object before updating addon hook 更新对象前
Object Hook Object after updating addon hook 更新对象后
Field Hook Field dependencies hook Field dynamic hook 字段级联
Field Hook Field search addon hook 字段搜索
Field Hook Field validation 字段校验
Field Hook Field default value 字段默认值
- Dynamic action enable logic 动态动作启用
- Dynamic action core logic 动态动作核心逻辑
- Dynamic action post logic 外部命令动态动作后处理
- Dynamic task enable logic 定时任务启用
- Dynamic task core logic 定时任务核心
- Form group enable logic 字段组显示

提示

动态逻辑相关各对象的 name 字段在创建后,即不可修改

# 动态权限

# 动态创建权限

对于对象动态创建权限的判断,需要创建的如下的Dynamic Object Hook 对象

Hook Type: 选择 Object create ability 
Object Type: 选择该客制化逻辑适用的对象类型
Core Logic: 选择具体的判断实现逻辑
1
2
3

# 注入变量

可以使用的注入变量如下表所示:

变量名称 变量类型 描述
objectType Class<?> 当前操作的对象类型
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包

# 返回结果

客制化代码需要返回一个 Map<String, Boolean> 对象,该对象包含一个 key 为 create 的元素,示例如下:

return ['create': true] //本行代码返回允许用户创建该对象

# 动态修改和删除权限

对于对象动态修改和删除权限的判断,需要创建如下的 Dynamic Object Hook 对象

Hook Type: 选择 Update/delete ability 
Object Type: 选择该客制化逻辑适用的对象类型
Core Logic: 选择具体的判断实现逻辑
1
2
3

和对应的 Dynamic Object Hook 对象

# 注入变量

客制化代码中,可以使用的注入变量如下表所示:

变量名称 变量类型 描述
objectType Class<?> 当前操作的对象类型
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
objectValue grails.core.GrailsDomainClass 请使用 object 参数 已弃用
object grails.core.GrailsDomainClass 当前操作的对象,类型为当前操作对象的类型
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包

# 返回结果

客制化代码需要返回一个 Map<String, Boolean> 对象,该对象包含 key 为 updatedelete 的元素,示例如下:

return [
  result: [
    'update': true,
    'delete': false
  ]
] //本行代码返回允许用户更新该对象,但不允许用户删除该对象
1
2
3
4
5

# 字段客制化

# 客制化字段默认值

除了支持使用 grails 模型定义的方式 (opens new window)指定字段保存到数据库中时的默认值, 系统还支持通过客制化指定创建表单中的字段的默认值。

# 注入变量

该客制化运行时,系统的注入变量如下表所列:

变量名称 变量类型 描述
objectType tech.muyan.DomainClass 当前操作的对象类型
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
destColumn java.lang.String 设定默认值的目标列
destColumnType java.lang.String 目标列的完整类型名称
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包

# 返回结果

该客制化运行的结果需要返回一个包含 key 为 "result" 的 Map 结果,如下所示

// result key 对应的 value 即为系统会传递到前台的默认值
return [result: xxx]
1

# 客制化字段校验

表单字段可以定义动态的校验逻辑,在前台表单中该字段发生修改时,会通过接口调用后台的校验逻辑进行校验, 如果校验不通过,则会在前台显示错误信息。

# 注入变量

动态字段校验逻辑执行时,系统注入的相关变量如下标所示

变量名称 变量类型 描述
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
domainName java.lang.String 当前操作的对象类型名称
destColumn java.lang.String 校验的目标列名称
destColumnValue java.lang.Object 目标列的值
create boolean 是否是创建操作,如为 fase 表示更新操作
requestData java.lang.Object 前台表单所有字段值的 json 序列化
objectId java.lang.Long 校验的对象 id, 对于创建操作,值为 -1
log Closure<?> 用于打印执行日志的 log 闭包

提示

requestData 的示例结构为:

{
  "formValues":{},  // 界面上显示的表单字段的值,包括当前在界面上还未保存到数据库中的值
  "record":{}, // 如果是更新操作,该值为当前记录保存在数据库中的值,如果是创建操作,该值为所有有默认值的字段的值
  "unique":[] // 与当前触发字段组合的唯一性校验的字段列表
}
1
2
3
4
5

可以使用 requestData.getAt("formValues").getAt("fieldName") 来获取界面上名为 fieldName 的字段的值

# 返回结果

客制化校验返回结果的结果如下所示, valid 表示校验是否通过, message 是校验失败时的提示信息,如果校验成功,该值会被忽略。

// result key 对应的 value 即为系统会传递到前台的默认值
return [
  valid: true | false,
  message: "校验失败的界面提示信息"
]
1
2
3
4

# 客制化字段联动

系统支持通过客制化的方式,实现字段之间的联动,可实现的逻辑包括:

  • 决定目标字段是否隐藏
  • 决定目标字段是否只读
  • 决定目标字段是否必填
  • 可以直接设定目标字段的值
  • 如果目标字段是选择类型,则可以设定其他选择字段的备选项

以下是向系统注入字段联动处理逻辑需要创建的 Dynamic Field Hook对象的概述:

Object Type: 选择该客制化逻辑适用的对象类型
Logic source: 逻辑来源,可选为 static field 或 dynamic field
Trigger field: 联动的触发字段,该字段的变化会触发联动
Target field: 联动的目标字段,即该客制化逻辑的目标字段
Trigger dynamic field: 联动的触发动态字段,该动态字段的变化会触发联动
Target dynamic field: 联动的目标动态字段,即该客制化逻辑的目标动态字段
Hook Type: 选择 Field Dependencies hook 
core Logic: 客制化代码,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3
4
5
6
7
8

提示

Trigger field 和 Trigger dynamic field 同时只能有一个字段设置值 Trigger field 和 Trigger dynamic field 同时只能有一个字段设置值

具体设置哪个字段的值由字段 Logic source 决定

# 注入变量

该客制化代码中,可使用的注入变量如下表所示:

变量名称 变量类型 描述
application grails.core.GrailsApplication 当前的 grails 应用上下文
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
domainName java.lang.String 不包含package部分的,当前操作的对象类型
sourceColumn java.lang.String 驱动列,该字段的变化触发了该客制化
destColumn java.lang.String 目标列,客制化的返回结果会作用于该列
sourceColumnValue java.lang.String 驱动列的值
object org.grails.web.json.JSONObject 用户正在编辑的对象当前界面上各个字段的值
log Closure<?> 用于打印执行日志的 log 闭包

提示

在客制化代码被调用的时点,注入的 object 对象只是界面上的输入框中,每一列的暂存数据, 还没有保存到后台的数据库中。

# 返回结果

该客制化代码需要返回的结果为一个多层的 Map 数据,具体返回数据及其说明如下:

return [
  //指定该字段的显示状态:hide 为隐藏、show 为显示且可编辑、readonly 为只读
  display: hide | show | readonly

  // 指定该字段是否必填, true 表示必填,false 表示非必填
  required: true | false

  // 指定该字段的值,如果是个多选字段,可以使用 [] 的形式来指定多个值
  value: [] 或者 xxx,

  // 如果该字段是个选择类型的字段,如下的返回值指定其备选项,
  // 每个备选项均包括显示给用户看的 Label 属性和实际保存的 value 属性
  options: [
      {
          "value": "ABSTRACT_DATE",
          "label": "Abstract date"
      },
      {
          "value": "ABSTRACT_DATE_TIME",
          "label": "Abstract date with time"
      }
  ]
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

提示

如果希望将某个选择字段的备选项清空,需要将 options 设置为空数组传递到前台,而不能传递 undefined 或者 null 到前台

{ 
  options: [] 
}
1
2
3

# 字段联动代码示例

如下的相关代码示例片段演示了一些可能在业务中用到的返回形式

  • 将客制化的目标字段设置为隐藏或只读
Map result = [:] //声明返回结果的 Map 对象

// 以下的两句代码是互斥的,否则后一句会覆盖前一句
result.put("display", "hide") // 设置目标字段在界面上隐藏
result.put("display", "readonly") // 设置目标字段在界面上为只读字段

result // 返回结果
1
2
3
4
5
6
  • 将客制化字段设置为显示,且必填
Map result = [:] //声明返回结果的 Map 对象

result.put("display", "show") //设置目标字段在界面上的正常显示
result.put("required", true) //设置目标字段为必填字段

result // 返回结果
1
2
3
4
5
  • 根据来源字段的值,修改目标字段的选项(这是一个很通用的,省市字段联动的示例)
Map result = [:] //声名返回结果的 Map 对象

result.put("display", "show") //设置目标字段在界面上的正常显示
result.put("required", true) //设置目标字段为必填字段

if (sourceColumnValue == "JS") { //如果来源字段的值是 "JS"
    result.put("options", [
        [value: "NJ", label: "南京"],
        [value: "HA", label: "淮安"],
    ]) // 将目标字段的选项设置为 [NJ, HA],显示分别为 "南京", "淮安"
    result.put("value", "NJ") //将目标字段的值设置为 "NJ"
}
result // 返回结果
1
2
3
4
5
6
7
8
9
10
11
12

# 真实系统中的使用示例

本章节列出了两个在正式系统中使用的,使用客制化代码控制表单行为的例子。

# 控制字段显示

创建 DynamicFieldDefinition 时根据 fieldType 控制 optionsJson 字段显示

# Dynamic Field Hook 对象
Target Field: opitonsJson
Logic source: Static field 
Object Type: Dynamic Field Definition
Field Name: fieldType
Hook Type: Field dependencies hook
Core logic: 关联的 Dynamic Logic 定义,可参考下述代码片段
1
2
3
4
5
6
# 客制化代码
def result = [:] //声明返回的 Map 对象
// 如果 fieldType 字段的值等于 OBJECT、FILE、BOOLEAN 或者 IMAGE
if (sourceColumnValue == tech.muyan.enums.FieldType.OBJECT.name()
    || sourceColumnValue == tech.muyan.enums.FieldType.FILE.name()
    || sourceColumnValue == tech.muyan.enums.FieldType.BOOLEAN.name()
    || sourceColumnValue == tech.muyan.enums.FieldType.IMAGE.name()) {
  result.put("display", "readonly") //将目标字段 optionsJson 设置为只读
} else {
  result.put("display", "show") //否则将目标字段 opitonsJson 设置为显示且可编辑
}
result //返回 result 对象作为客制化逻辑执行的结果
1
2
3
4
5
6
7
8
9
10

# 控制选择字段的备选项

以下的客制化配置和代码,实现了在创建 Object Dynamic Field 对象时,根据用户选择的 dynamicField 字段, 控制 displayComponentType 字段中的备选项。

# Dynamic Logic 对象
Target Field: opitonsJson
Object Type: Dynamic Field Definition
Field Name: fieldType
Logic Type: Field dependencies
Code: 参考下述代码片段
1
2
3
4
5
# 客制化代码
def result = ["display": "show"] //默认显示该字段
def options = [] //声明返回的 options 数组

/// 从传递到后台的 sourceCOlumnValue 字段,在这里是 dynamicFieldDefinition 的 id 字段获取对象
def dynamicField = tech.muyan.DynamicFieldDefinition.get(Long.valueOf(sourceColumnValue))

if (null != dynamicField) {
  def fieldType = dynamicField.fieldType //获取对象中保存的 fieldType 信息

  //从配置的映射表中获取每一种 fieldType 可以使用的 displayComponentType 列表
  def optionConfig = tech.muyan.config.DynamicFieldTypeToComponentMapping.FieldTypeToComponentMapping.get(fieldType)
  if (optionConfig != null && optionConfig.size() > 0) {
    //遍历该列表,将枚举值的 name 和 label 分别放入 options 列表
    for (option in optionConfig) {
      options.add(Map.of("value", option.name(), "label", option.label))
    }
    result.put("options", options) //将 options 数组放入 result map
  }
}
result //返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 字段快捷搜索逻辑

系统支持通过客制化的方式,定制某个对象的页面上,引用其他对象的时候,快捷搜索的过滤逻辑,具体

对于客制化搜索逻辑,需要创建如下的Dynamic Field Hook对象

Target Field: 设置为待搜索的对象字段
Object Type: 选择该客制化逻辑适用的对象类型,这里要设置字段所属于的对象的类型, 而不是待搜索对象的类型
Hook Type: 选择 Field search addon hook 
Core Logic: 客制化代码,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3
4

# 注入变量

该客制化代码中,可使用的注入变量如下表所示:

变量名称 变量类型 描述
ownerClass java.lang.Class<?> 该字段所属的对象类型
fieldName java.lang.String 在对象中,待搜索字段的字段名称
fieldClass java.lang.Class<?> 待搜索字段的字段类型
keyword java.lang.String 搜索关键字
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包

# 返回结果

该客制化代码需要返回的结果为一个 Map 数据类型,该 Map 包含一个 key 为 result, value 为 domain 对象列表的元素,具体返回数据示例如下:

return [
  // 包含且仅包含一个 key 为 result 的数组
  'result': [// result 中是一个数组
    // 数组中每个元素都是一个 domain 对象
  ]
]
1
2
3
4
5

# Action 及 Wizard 字段客制化

Action 和 Wizard 中所使用的动态字段也可支持 DynamicFieldHook 客制化,在创建供 Action 和 Wizard 使用的字段客制化 DynamicFieldHook 对象时,需要注意如下几点:

  1. triggerField 应该设置为 df_<DynamicFieldDefinition 字段 name 属性>
  2. logicSource 应该设置为 DYNAMIC_FIELD
  3. objectType 应设置为空,因为该客制化逻辑不是针对某个对象类型的,而是针对某个 动态字段的。如果通过导入 CSV 方式创建 DynamicFieldHook 对象,可以将 objectType.shortName 列设置为空。

如下是一个实际项目中的例子,在 用户 这个字段上,定义了名为

// DynamicFieldDefinition.csv
// 定义一个动态字段,其 name 为 "用户"
name(*),fieldType,optionsJson,label,referenceClazz.fullName
用户,OBJECT,,用户,tech.muyan.security.User

// DynamicFieldInstance.csv
// 定义一个动态字段实例,关联到上述 name 为 "用户" 的动态字段定义,
// 其 type 为 "Action parameter field", 关联到 name 为 "BatchAddUserToGroupInUserGroupList" 的 Action
// type,label,displayComponentType,dynamicField.name(*),objectType.shortName(*),action.name,wizardStep.name,editable,display,required,displaySequence
Action parameter field,用户,OBJECT_MULTIPLE_SELECTION,用户,,BatchAddUserToGroupInUserGroupList,,true,true,true,10

// DynamicFieldHook.csv 
// 定义一个动态字段客制化,objectType.shortName 为空,其 triggerField 为 "df_用户", logicSource 为 "DYNAMIC_FIELD"
organization.name,objectType.shortName,coreLogic.name,hookType,triggerField,targetField,triggerDynamicField.label,targetDynamicField.label,logicSource,name(*),active,isSystem,description
$ROOT_ORG$,,User search,SEARCH,,df_用户,,,DYNAMIC_FIELD,User search in bulk add user to group action,Y,Y,"批量加用户到用户组 Action 中的用户搜索"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意

注意如果多个 Action 或 Wizard 使用了同一个动态字段定义(同一个 DynamicFieldDefinition),且同时定义了 DynamicFieldHook,因系统使用 df_<DynamicFieldDefinition Name> 来识别字段客制化,因此系统无法区分不同的 Action 或 Wizard 对应的字段的客制化。

这种情况下,需要创建多个 DynamicFieldDefinition 对象,每个 Action 或 Wizard 的 字段实例请分别基于不同的 DynamicFieldDefinition 定义进行创建。

# 对象生命周期客制化

# 创建

系统支持通过客制化的方式,定制某个对象创建时的额外逻辑,可以在如下节点插入客制化逻辑:

  1. 创建保存前:对象已经在内存中创建,所有的对象属性均构建完成,但还没有被保存到数据库中。
  2. 创建保存后:对象的保存方法已经被调用,但事务还没有提交前。

对于如上所述的两种客制化,需要分别创建如下的Dynamic Object Hook对象

Object Type: 选择该客制化逻辑适用的对象类型
Hook Type: 选择 "Before creating" 
Core Logic: 客制化代码,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3
Object Type: 选择该客制化逻辑适用的对象类型
Hook Type: 选择 "After creating" 
Core Logic: 客制化代码,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3

该客制化代码中,可使用的注入变量如下表所示:

变量名称 变量类型 描述
object <? extends GormEntity> 对象实例
dynamicFieldValues List<tech.muyan.dynamic.field.DynamicFieldValue> 所有动态字段的值
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包
hookType tech.muyan.enums.ObjectHookType 当前执行的 object hook 的类型

返回结果及异常请参考下述返回结果及异常处理章节描述。

注意

dynamicFieldDefinition 参数只在创建保存后的客制化点可用

# 更新

系统支持通过客制化的方式,定制某个对象更新时的额外逻辑,可以在如下节点插入客制化逻辑:

  1. 更新前:对象已经在内存中更新,包括动态属性在内的所有的对象属性均已被更新,但还没有被保存到数据库中。
  2. 更新后:对象及所有动态字段的更新方法已经被调用,但事务还没有提交前。

对于上所述的两种客制化,需要分别创建如下的Dynamic Object Hook对象

Object Type: 选择该客制化逻辑适用的对象类型
Hook Type: 选择 "Before updating" 
Core logic: 客制化代码定义,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3
Object Type: 选择该客制化逻辑适用的对象类型
Hook Type: 选择 "After updating" 
Core logic: 客制化代码定义,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3

该客制化代码中,可使用的注入变量如下表所示:

变量名称 变量类型 描述
oldObject <? extends GormEntity> 更新前的对象实例的拷贝
newObject <? extends GormEntity> 包括动态字段的,更新后的对象实例
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包
hookType tech.muyan.enums.ObjectHookType 当前执行的 object hook 的类型

返回结果及异常请参考下述返回结果及异常处理章节描述。

# 删除

系统支持通过客制化的方式,定制某个对象删除时的额外逻辑,可以在如下节点插入客制化逻辑:

  1. 删除前:对象被从数据库中查询出来,对象的删除方法被调用前
  2. 删除后:对象的删除方法被调用后,事务提交前

对于如上所述的两种客制化,需要分别创建如下的Dynamic Object Hook对象

Object Type: 选择该客制化逻辑适用的对象类型
Hook Type: 选择 "Before deleting" 
Core logic: 客制化代码定义,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3
Object Type: 选择该客制化逻辑适用的对象类型
Hook Type: 选择 "After deleting" 
Core logic: 客制化代码定义,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3

该客制化代码中,可使用的注入变量如下表所示:

变量名称 变量类型 描述
object <? extends GormEntity> 待删除的对象
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包
hookType tech.muyan.enums.ObjectHookType 当前执行的 object hook 的类型

返回结果及异常请参考下述返回结果及异常处理章节描述。

# 返回结果及异常处理

在操作前的客制化逻辑中,可以直接修改操作的对象实例参数实现客制化,对对象实例的修 改会被保存到数据库中。

在操作前和操作后的客制化代码的逻辑执行中,均可使用抛出异常的方式中断系统对该对象 的操作过程,详述如下:

  • tech.muyan.exception.CustomLogicInterruptException 或其子类型的异常被捕获 到,则该对象的操作过程会被中断,该操作不会被保存到数据库中,且该异常的 Message 会 被在前台作为错误显示给用户。

  • tech.muyan.exception.CustomLogicWarningException 或其子类型的异常被捕获到, 则该对象的操作过程不会被中断,但该异常的 Message 会被在前台作为警告显示给用户。

提示

在创建保存前的客制化点,对象的 id 字段为空,在创建保存后的客制化点,对象的 id 字 段已经获取到值。

# 接口返回数据客制化

系统支持通过客制化的方式,定制对象 API 接口返回前台的某个对象的数据的结构和值。 创建如下的Dynamic Object Hook对象即可实现本客制化。

Object Type: 选择该客制化逻辑适用的对象类型
Logic Type: 选择 "Object render" 
Core logic: 客制化代码,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3
  • 注入变量

该客制化代码中,可使用的注入变量如下表所示:

变量名称 变量类型 描述
object <? extends GormEntity> 接口待返回的对象实例
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
page tech.muyan.enums.CustomRenderPageType 调用该 render 方法的页面
log Closure<?> 用于打印执行日志的 log 闭包
ownerClass java.lang.Class<? extends GormEntity> 对于搜索页面, 搜索字段所属对象
fieldName java.lang.String 对于搜索页面, 搜索字段的字段名
fetchType tech.muyan.enums.FetchType 获取数据的类型

fetchType 参数是根据界面的不同业务场景,选择返回的数据的范围,当前可选值说明如下:

说明
ONLY_LABEL_FIELD 只获取 Label 字段和 id
EXCLUDE_ARRAY_COLUMNS 返回除了一对多和多对多的关联对象外的其他字段值
ALL_COLUMNS 返回所有字段值

page 参数的可选值如下:

说明
RELATED_SEARCH 关联对象列表页面带搜索条件
RELATED 关联对象列表页面带
LIST_SEARCH 主列表页面带搜索条件
LIST 主列表页面
FINDER 搜索页面
FINDER_SEARCH 搜索页面带搜索条件
DETAIL 详情或者编辑页面
SHOW_MULTIPLE Show multiple API 的返回值

注意

在非搜索结果渲染的场景下, 注入的 ownerClass 字段为空,在实现 render API 时,请注意兼容。

  • 返回结果

该客制化代码需要返回的结果为一个 Map 数据类型或者为一个 org.grails.datastore.gorm.GormEntity 对象的实例。

当返回值为 Map 类型时,Map 的 Key 为 "result", Map 的Value 为另外一个 Map, 这个 内层 Map 的 key 为各字段名称,value 为字段的值。系统会向调用该 API 的客户端返回 该 Map 数据的 JSON 表示。

// 该客制化代码需要返回的结果为一个 Map, key 为 result, value 中为 [返回字段名: 字段值] 的 Map 的格式返回结果
[
  result: [
    columnName: columnValue,
    // 对象类型的子字段返回一个只包含 id 的子 map
    objectField: [
      id: objectId
    ],
    // 用于 card list view 进行 HTML 渲染的属性
    "@HTML_CONTENT@": ""
  ]
]
// 或者直接为一个 org.grails.datastore.gorm.GormEntity 对象的实例
return object
1
2
3
4
5
6
7
8
9
10
11
12
13

系统限制

  • 当前该客制化只作用于不包含动态字段的实体类。
  • 卡片列表渲染

在表格渲染界面,用户可以在表格和卡片显示模式之间切换,

特别的,如果 render API 的返回数据中,包含名为 @HTML_CONTENT@ 的属性,则在渲染 card list 视图时,系统会直接在卡片中,渲染该属性的值,而不是以表格形式显示该对象 的详情。

# 对象详情访问

系统支持通过客制化的方式,在对象详情被查看时,调用客制化逻辑,一个比较直观的应用 场景为对象访问的计数器。

当前本客制化的调用点为: 对象通过 /show/<domainId> 接口被访问时

对于本客制化,需要创建如下的 Dynamic Object Hook 对象

Object Type: 选择该客制化逻辑适用的对象类型
Hook Type: 选择 "Object access" 
Code: 客制化代码,具体代码中可使用的注入变量和返回值约定参见下述文档
1
2
3

该客制化代码中,可使用的注入变量如下表所示:

变量名称 变量类型 描述
object <? extends GormEntity> 对象实例
renderedObject <? extends GormEntity> 或 Map<String, Object> Render API 返回的对象实例或包含对象数据的 Map 对象
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
fetchType tech.muyan.enums.FetchType 获取数据的类型
log Closure<?> 用于打印执行日志的 log 闭包
hookType tech.muyan.enums.ObjectHookType.ACCESS 当前执行的 object hook 的类型

提示

本客制化没有返回值,且不应该修改待访问对象。ACCESS 客制化运行不应该修改待渲染对 象的值,而应该仅仅进行诸如对象访问计数等类型的逻辑实现。

提示

本客制化点与 render API 的区别在于, 本客制化点只在对象的详情被请求时才会被调用,对应到系统代码,是 DomainDataController.show 方法被调用时。 而 render API 在各种对象的数据被请求的场景,如对象列表,创建,更新对象后返回数据,搜索返回对象列表等场景,均会被调用。

# 附件存储及返回客制化

为支持病毒扫描,加密,解密等需求,系统支持在写入附件内容和读取附件内容时,调用客 制化代码来进行附件内容的变换。 这种变换通过 StorageFieldValue 对象的 RENDER Api 来实现。

在创建附件时,在 render 客制化代码中,可通过 object.getData() 方法获取原始的附 件文件数据,通过设置同一对象的data 属性,即可将变换后的文件数据注入到保存节点, 并保存在文件存储引擎中。

在读取附件时,在 render 代码中,可通过 object.getData() 方法获取存储在存储引擎 中的文件数据,通过设置同一对象的 data 属性,即可将变换后的文件数据注入到返回数据 的节点,并返回给调用方。

具体客制化 API 的注入参数可参考 接口返回数据客制化 章节。

# 动态动作 ( Action)

# 定义

动态动作使用 Dynamic Action 对象注入系统,可通过页面菜单 Development > Dynamic actions 在界面直接创建该对象,并输入 Action 的执行代码,亦可通过 CSV 文件导入 Action 的定义。

创建 Action 对象的字段详述如下:

Name: 名称,不为空,唯一
Label: 界面显示的 Label
Description(helpText): 描述,同时用于在前台显示给用户的帮助信息
Type: Action 的类型
Icon: 显示图标
Scope: Action 的适用范围
async: 是否为异步 Action, 当前未使用
Confirm Type: 在前台执行前如何显示确认或者提示信息给用户
Confirm message: 在前台执行前显示的确认和提示信息文本
Enable for roles: 对哪些角色用户启用
Enable logic: 启用的动态逻辑,代码中可使用的注入变量和返回值约定参见下述文档
Core logic: 具体执行的动态逻辑,只对 groovy 类型的 Action 生效,代码中可使用的注入变量和返回值约定参见下述文档
External command: 外部命令,只对 External Command 类型的 Action 生效
Post logic: 执行的后处理,只对 External Command 类型的 Action 生效
Is system: Action 是否为系统内置的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 类型

当前支持的 Action 类型如下:

  • GROOVY_CODE Groovy 代码, 使用一段 Groovy 代码作为 Action 执行的逻辑
  • OS_COMMAND 操作系统命令,执行操作系统命令并捕获 SystemErr 和 SystemOut 的输 出作为 Action 执行的结果

# 适用范围(Action 模式)

当前支持的模式如下:

  • OBJECT_SINGLE 可在某一个 Object 之上运行
  • OBJECT_SINGLE_MULTIPLE 可在某一个或者多个 Object 之上运行
  • OBJECT_MULTIPLE 可在多个 Object 之上运行
  • CLASS_LEVEL 可作用于某种 Domain, 而不是具体的对象

# 提示信息显示模式

当前支持的提示信息显示模式如下:

  • NO_CONFIRM 无需提示,用户在前台点击 Action 链接后,直接运行 Action
  • NO_POPUP_NO_CONFIRM 不弹出运行的 Popover 控件,点击后,直接调用后台 API 运行 Action, 完成后,刷新 Action 列表
  • DISPLAY_CONFIRM 在前台显示一个确认控件,用户需点击该确认控件的确认按钮后,才 会执行该 Action

# Action 与 Domain 的关联

Dynamic Action 本身并不与 Domain 直接关联,通过 DynamicActionDomainClass 对象关联,该对象包含如下字段:

  • dynamicAction: Dynamic Action 对象
  • domainClass: Domain 对象
  • displaySequence: 该 Action 在界面上的显示顺序, displaySequence 值越小,显示越靠前
  • group: DynamicActionGroup 对象,表示该 Action 在界面上的分组,同一分组 Action 会被放在同一个下拉菜单中显示。

# 启用逻辑

系统会在返回 Action 列表给前台前,运行 Action 定义的 Enable Logic 代码, 以决定该 Action 针对当前场景是否启用,Enable Logic 逻辑在系统中,以 Logic Type 为 DYNAMIC_ACTION_ENABLE_LOGIC 类型的 DynamicLogic 进行定义。

# 注入变量

代码运行时,系统会注入如下的上下文变量

变量名称 变量类型 描述
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
action tech.muyan.dynamic.action.DynamicAction 运行的 action 定义
objectIds List<java.lang.Long> 目标 domain 对象 id 列表
objects List<? extends GormEntity> 目标 domain 对象列表
objectType tech.muyan.DomainClass 目标 domain 对象的类型信息
parameters Map<String, Object> 用户前台输入的 action 运行参数(仅 core Logic 可用)
ownerInfo tech.muyan.importexport.OwnerInfo 当前显示列表的关联主对象
log Closure<?> 用于打印执行日志的 log 闭包

关于显示图标的说明请参考 图标说明 章节

ownerInfo 参数说明: 如果当前的显示和执行上下文是在显示某个主对象的关联对象的列表时, ownerInfo 参数会包含如下信息:

  • ownerId: 主对象的 id
  • ownerClassName: 主对象的类型
  • columnNameInOwnerClass: 关联对象在主对象中的属性名

举例说明: 如果当前显示的是某个 Order 对象的 OrderItem 列表,那么 ownerInfo 参数会包含如下信息:

  • ownerId: Order 对象的 id
  • ownerClassName: Order 对象的类型
  • columnNameInOwnerClass: OrderItem 对象在 Order 对象中的属性名

提示

parameters 参数只在 core logic 执行中才会被注入,在 enable Logic 的执行中,不会被注入

注意

对于选中多条记录显示 Action 列表,或者执行 Action 的场景,注入到执行代码中的 objects 是所选中对象的列表,

在某一条对象上显示 Action 列表,或者执行 Action 时,传递的 objects 参数也是一个数组,数组中只有一个元素。

注意

以下场景下,运行 Enable Logic 代码,获取可用 Action 列表时,注入的 objectIds 和 objects 对象为空,在实现 Enable Logic 时,需注意兼容此种场景。

  • 用户进入列表页面,前台获取可用的 CLASS_LEVEL Action 列表时;

针对选中多条记录运行 Action 的场景,系统会显示经 Enable Logic 逻辑判断后,所有 OBJECT_SINGLE, OBJECT_SINGLE_MULTIPLE 和 OBJECT_MULTIPLE 模式下可用 Action 的全集。

提示

在 CSV 文件中导入 Action 定义时,Description 字段需使用 helpText 作为列名称

# 返回结果

Enable Logic 运行后的返回结果的结构如下

// 表示该 action 或 task 或 widget 是否启用
[result: true | false]
1

# 运行及结果返回

执行结果保存在对象 DynamicActionExecRecord 中,动作执行的可能状态有:

  • NOT_START 执行未开始
  • RUNNING 执行中
  • SUCCESS 执行成功
  • FAILED 执行失败
  • SUCCESS_WITH_WARNING 执行完成但有警告

# GROOVY_CODE 动作

# 注入变量

GROOVY_CODE 类型的 Action, 执行的具体逻辑以一个 Type 为 DYNAMIC_ACTION_LOGIC 的 Dynamic Logic 进行定义,执行时,系统会注入如下的上下文变量

变量名称 变量类型 描述
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
action tech.muyan.dynamic.action.DynamicAction 当前待判断的 action 定义
object <? extends GormEntity> 当前待判断的 domain 对象
objectType tech.muyan.DomainClass 当前待判断的 domain 对象的类型信息
classLoader java.lang.ClassLoader 运行客制化代码的调用方 ClassLoader
log Closure<?> 用于打印执行日志的 log 闭包
# 返回结果

返回结果的约定请参考章节 执行结果返回约定 章节。

# OS_COMMAND 动作

# 命令替换

针对 OS_COMMAND 类型的 Action,执行时,系统会使用如下规则,替换执行的命令

占位符 替换目标
${username} 用户的用户名
${roles} 用户的角色列表,逗号分隔
${object} Domain Object 的 JSON 序列化字符串
${parameterName} 前台传入的用户输入参数

提示

对于日期类型的参数,在做参数替换时,会使用 "yyyy-MM-dd HH:mm:ss z" 格式来格式化 用户输入的参数。

# 结果保存

OS_COMMAND 类型的 Action 执行完成后,系统会自动捕获命令行输出作为结果,具体规则 如下:

  1. 如果该 Dynamic Action 定义了 Post Logic, 则以 Post Logic 执行的结果作为返回值,具 体 Post Logic 的运行情况参考章节 后处理

  2. 如果该 Dynamic Action 没有定义 Post Logic, 则遵循如下的返回逻辑:

  3. 如果命令的 exit code 为0, 且在执行过程中,执行框架没有捕获任何异常,则执行结 果为 SUCCESS

  4. 如果命令的 exit code 为非0, 且在执行过程中,执行框架没有捕获任何异常,则执行 结果为 SUCCESS_WITH_WARNING

  5. 重定向到标准输出的 SystemError 流的输出会被捕获到执行结果的 execLog 中,作为 执行日志。

  6. 重定向到标准输出的 SystemOut 流的输出会被捕获到执行结果的 execResult 中,作为 执行结果。

# 后处理

针对 OS_COMMAND 类型的 Action, 可以定义一个后处理逻辑,用来处理外部命令执行的结 果,该后处理以一个 type 为 DYNAMIC_ACTION_POST_LOGIC 的 Dynamic Logic 进行定义, 执行该后处理时,系统会注入如下的上下文变量:

变量名称 变量类型 描述
userContext grails.plugin.springsecurity.userdetails.GrailsUser 当前操作的用户信息
application grails.core.GrailsApplication 当前的 grails 应用上下文
action tech.muyan.dynamic.action.DynamicAction 当前待判断的 action 定义
object <? extends GormEntity> 当前待判断的 domain 对象
objectType tech.muyan.DomainClass 当前待判断的 domain 对象的类型信息
stdout java.lang.String 外部命令运行的标准输出流
stderr java.lang.String 外部命令运行的标准错误输出流
classLoader java.lang.ClassLoader 运行客制化代码的调用方 ClassLoader
log Closure<?> 用于打印执行日志的 log 闭包

后处理逻辑返回结果的约定请参考章节 执行结果返回约定 章节。

# 执行结果返回约定

GROOVY_CODE 类型 Action 的 核心逻辑OS_COMMAND 类型 Action 的 后处理逻辑 的返回结果的约定如下:

  1. 如代码正常执行返回,无警告或错误,则执行代码需要返回一个如下结构的结果对象
// execResult 为字符类型
// redirect 为字符类型
[
  execResult: "执行的结果"
  redirect: "执行后的跳转页面"
]
1
2
3
4
5
  1. 如代码运行过程中,希望将该 Action 的状态设置为成功但有警告,则执行代码需要抛 出 tech.muyan.exception.CustomLogicWarningException,如果希望同时返回执行结 果,则可以调用 CustomLogicWarningException 的构造函数 CustomLogicWarningException(String message, Object data),将 data 作为结果 传递给执行框架,执行框架会将该信息保存到执行结果字段 execResult 中。

  2. 如果代码运行过程中,抛出了任何非 tech.muyan.exception.CustomLogicWarningException 类型的异常,则系统会将该 Action 的执行结果标记为失败,且将该异常的 getMessage() 方法返回值或 getMessage() 返回值为空时,toString() 方法返回值记录在执行的 Log 中

  3. 如果希望代码运行完成后,在前台,跳转到指定页面,则需要在返回结果的 Map 中,包 括 key 为 redirect, value 为 String 的元素。

# 可用 extInfo 列表

如下是 DynamicAction 可用的 extInfo 列表,用于控制 DynamicAction 显示及执行的行为

{
  /** 前台是否显示该 Action 的 label, 默认为 true, 如果只希望显示 Action 图标,可设置 displayLabel 为 false */
  "displayLabel"?: true | false,
  /** 是否在执行该 Action 后,刷新当前页面, 默认为 true, 如果 action 执行不更新显示数据,可以无需刷新页面 */
  "refreshPage"?: true | false
}
1
2
3
4
5

# 定时任务

定时任务用于定义系统中需要定时执行的相关任务或者逻辑,如发送合同过期通知,定期归档数据等。

# 启用逻辑

定时任务中可以设定 enableLogic, 用于决定定时任务是否需要运行,定时任务启用的动态 逻辑的输入和输出参数如下所述:

# 输入参数

变量名称 变量类型 描述
triggerDatetime java.time.LocalDateTime 定时任务的触发时间
task tech.muyan.dynamic.task.DynamicTask 触发定时任务实例
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包

# 返回结果

Enable Logic 运行后的返回结果的结构如下

// 表示该 action 或 task 或 widget 是否启用
[result: true | false]
1

# 核心逻辑

定时任务核心逻辑即定时任务运行时执行的具体代码,其输入和返回值如下所述:

# 输入参数

变量名称 变量类型 描述
triggerDatetime java.time.LocalDateTime 定时任务的触发时间
task tech.muyan.dynamic.task.DynamicTask 触发定时任务实例
application grails.core.GrailsApplication 当前的 grails 应用上下文
log Closure<?> 用于打印执行日志的 log 闭包

提示

当重跑定时任务时,会使用原任务运行时的触发时间作为参数

# 返回结果

返回结果是一个 Map 结构, 如下是定时任务执行后的返回结果的结构:

return [
  execResult: 'OK, Result' //执行结果,类型为文本
]
1
2

# 界面显示 CSS 定制

系统提供了 DynamicTheme 对象,用以附加 CSS 规则供前台显示使用,可以在不修改前台 代码的情况下,对界面显示风格做一定定制。

该对象的创建在菜单 开发 > 界面显示 > 显示主题 下。

创建了 DynamicTheme 对象后,使用具有 developer 权限的用户登陆,选中记录,执行 Active DynamicTheme Action 即可使其生效。

提示

同时只能有一个激活生效的 DynamicTheme 对象,某一个 DynamicTheme 生效后, 系统会自动将所有其他 DynamicTheme 标记为不生效

# 动态服务定制

# 动态服务提供者

动态服务是指由系统对外提供的服务,目前支持通过标准 HTTP 协议以及在 JVM 内部调用,以实现类似 RPC 方式的调用。

首先需要定义一个 DynamicServiceProvider 对象,该对象的创建在菜单 开发定制 -> 动态服务 -> 服务提供者 下。一个 DynamicServiceProvider 对象包含如下字段:

变量名称 变量类型 描述
name String 服务唯一名称
logic tech.muyan.dynamic.DynamicLogic 服务的执行逻辑
active Boolean 是否激活

其中 logic 字段是一个 DynamicLogic 对象,用于定义服务的执行逻辑,该逻辑的类型为 DYNAMIC_SERVICE_CORE_LOGIC。该逻辑执行时会注入以下参数:

变量名称 变量类型 描述
user tech.muyan.security.User 执行用户
params Map 执行参数
protocol tech.muyan.enums.DynamicServiceProtocol 调用当前服务的方法,目前仅支持 HTTP 和 JVM
log Closure<?> 用于打印执行日志的 log 闭包

配置好动态服务后还需要基于 User 创建对应的用户令牌,该用户令牌用于调用动态服务时的身份认证,用户令牌的创建在菜单 开发定制 -> 动态服务 -> 用户令牌 下,通过执行 生成用户令牌 Action 创建用户令牌,该用户令牌的创建需要指定对应的用户。

针对已经创建的用户令牌,可以设置其 是否生效中 字段用于启用或者禁用该用户令牌。

# 平台内动态服务调用

如果是基于本平台开发的应用,可以使用已经封装好的 DynamicServiceConsumerService 类来调用动态服务,该类的使用方法如下:

import tech.muyan.BeanHelper
import tech.muyan.service.DynamicServiceConsumerService

DynamicServiceConsumerService dynamicServiceConsumerService = BeanHelper.getBean(DynamicServiceConsumerService)
def res = dynamicServiceConsumerService.invoke(serviceName, params, userToken)
1
2
3
4

DynamicServiceConsumerService 的调用过程分为两部分,第一部分是服务发现,第二部分是服务调用。

# 动态服务发现

服务发现是指根据服务名称,从系统中查找对应的服务提供者。目前支持两种协议 HTTPJVM,如果是 HTTP 协议,则返回服务提供者的 URL,如果是 JVM 协议,则直接尝试通过 DynamicServiceProvider 调用服务提供者的逻辑。

HTTP 服务发现是基于系统配置 DynamicConfig 中的 dynamicService.http.hosts 去定位的,它的 Value 是一个以 , 分隔的字符串,用于指定 HTTP 服务提供者的地址,如 http://localhost:8080/service,https://muyan.cloud/service。URL中的 service 是固定前缀。

# 动态服务调用

调用动态服务时,需要指定服务名称,调用参数和用户令牌,其中服务名称需要符合以下几种类型:

  • 直接填入服务名称, DynamicServiceConsumerService 会自动根据服务名称去服务发现中查找对应的服务提供者,如果找到多个服务提供者,则会随机选择一个服务提供者进行调用。
  • jvm:// 开头的 uri 表示调用 JVM 服务,如 jvm://some_service,其中 some_service 是服务名。
  • http:// 或者 https:// 开头的 uri 表示调用 HTTP 服务,如 http://host/service/some_service,其中 service 是服务端域名, service 为固定 URL 前缀,some_service 为服务名。
    • 如果只是想指定使用 HTTP 作为服务调用方式,但是不想指定具体使用哪台服务提供者,则可以使用 http://ANY_PROVIDER/some_service 的方式,其中 ANY_PROVIDER 表示由 DynamicServiceConsumerService 的服务发现来决定具体的调用方。

# 第三方动态服务调用

如果是第三方客户端想接入本平台的动态服务,则需要自行通过 HTTP 协议实现服务发现和服务调用的逻辑:

  • 客户端需要指定调用的服务端地址如 http://host/service/some_service,使用 POST 请求
  • 带上一个 X-MY-Token 的 HEADER,其值为用户令牌
  • 参数以 JSON 格式填入 body 中

提示

需要注意服务名需要经过 url 编码以避免空格字符等特殊字符导致的问题

最后更新: 2024/2/23 07:44:20