# 动态服务

动态服务用于把一段动态逻辑发布成 HTTP 接口,供前端页面、移动端或第三方系统调用。服务的逻辑在运行时定义和修改,无需重新编译和部署。

升级注意(1.0.0-beta18)

beta18(#768)起,Enable Anonymous(enableAnonymous)为关闭或未设置的动态服务会拒绝未登录的请求,返回 errorCode: 11004(AnonymousInvocation)。

此前这个开关约一年没有生效(2025-09 平台改用匿名身份对象表示未登录用户之后,判断 auth == null 一直不成立),未登录也能调用任意动态服务。所以升级到 beta18 后,一直被匿名调用的服务会突然失败。

升级前请逐个排查:

  1. 确实需要匿名访问的服务(例如公开页面调用的接口),把 Enable Anonymous 打开;
  2. 其余服务的调用方改为携带 token 调用,见 调用方式与认证。

对开启了 Enable Log 的服务,可以在数据库中用下面的 SQL 找出曾被匿名调用、升级后会被拒绝的服务(没有开启 Enable Log 的服务不会留下执行记录,需要从调用方排查):

select s.name, s.enable_anonymous, count(*) as anonymous_calls, max(r.start_time) as last_call
from dynamic_service_exec_record r
join dynamic_service s on s.id = r.provider_id
where coalesce(s.enable_anonymous, false) = false
  and (r.exec_auth is null or r.exec_auth::text like '%Anonymous%')
group by 1, 2 order by 3 desc;
1
2
3
4
5
6

注意:被拒绝时 HTTP 状态码当前是 500(不是 403),客户端请按响应体中的 errorCode 判断,见 错误码。

# 目标读者

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

# 定义动态服务

菜单位置:开发定制 > 系统集成 > 动态服务

动态服务列表

截图中的“设备状态查询”是本页示例用的服务(Active、Enable Log 开启,Enable Anonymous 关闭);“Test Echo”是平台项目模板自带的示例服务(来自模板的 data/csv/DynamicService_example.csv),新建的系统里都会有。

界面显示 属性名 类型 说明
Name name String 服务名称,租户内唯一,创建后不可修改;调用地址中使用这个名称
Logic logic tech.muyan.dynamic.DynamicLogic 服务的执行逻辑,逻辑类型为 DYNAMIC_SERVICE_CORE_LOGIC
Active active Boolean 是否激活
Enable Anonymous enableAnonymous Boolean 是否允许未登录调用,默认关闭
Enable Log enableLog Boolean 是否记录执行记录,默认关闭;只有开启后才会生成 执行记录
Body Type bodyType 枚举 请求体的解析方式:JSON、XML、INPUT_STREAM;不填时按 JSON 处理
Exec Records execRecords 该服务的执行记录

新建动态服务:

  1. 在 开发定制 > 动态逻辑 > 动态逻辑 中创建一个类型为 DYNAMIC_SERVICE_CORE_LOGIC(动态服务核心逻辑)的动态逻辑。
  2. 在动态服务列表页点击 创建,填写名称,选择上一步的逻辑,打开 Active,按需设置其他开关后保存。

新建动态服务

截图中的服务名称、逻辑与下文示例一致。弹窗标题显示为领域类名 DynamicService,这是当前版本的界面问题。

# 注入变量

服务逻辑执行时,可以使用以下注入变量:

变量名称 变量类型 描述
user tech.muyan.api.security.MuyanAuthentication 调用者。未登录调用时是匿名身份对象,不是 null
method String 请求的 HTTP 方法,例如 GET、POST
params Map<String, Object> URL 参数。只有一个值的参数直接是字符串,有多个值的参数是列表
body Object 请求体,按 Body Type 解析:JSON 为 JSON 对象,XML 为 XML 对象,INPUT_STREAM 为原始输入流
log Closure<?> 用于打印执行日志的 log 闭包,开启 Enable Log 后日志写进执行记录

提示

URL 里的 access_token 参数也会出现在 params 中。

# 返回结果

逻辑应返回一个包含 status 和 body 两个键的 Map:

  • status:返回给调用方的 HTTP 状态码,必须是整数,不填时为 200;
  • body:返回给调用方的内容,会被转成 JSON。不填,或者是空列表、空字符串、0、false 时返回 {}。

注意

平台只取返回值中的 status 和 body,其他键会被丢弃;status 的值不是数字时调用会失败(HTTP 500,For input string: ...)。不要把业务上的状态字段直接放在返回值的顶层,而要放进 body。

示例:按设备编号返回设备运行状态。

log("收到请求:method=${method}, params=${params}")
String code = params.equipmentCode ?: body?.equipmentCode
if (!code) {
  return [status: 400, body: [message: '缺少参数 equipmentCode']]
}
return [
  status: 200,
  body  : [
    equipmentCode: code,
    runningStatus: '运行中',
    caller       : user.name,
    method       : method,
  ]
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 调用方式与认证

调用地址为:

http://<服务器地址>/api/service/<服务名>
1
  • 服务名如果含中文、空格等字符,需要进行 URL 编码。
  • 不限制 HTTP 方法,GET、POST、PUT、DELETE 都会执行同一个逻辑,逻辑中可以通过 method 区分。
  • 认证方式与平台其他 REST API 相同:先 登录 获取 access_token,再通过请求头 Authorization: Bearer <access_token>、URL 参数 access_token 或 Cookie 携带,详见 在请求中携带 token。旧版的用户令牌和 X-MY-Token 请求头 1.0 起已移除。
  • Enable Anonymous 开启后,不带 token 也可以调用,此时 user 为匿名身份。

以示例服务“设备状态查询”为例(Enable Anonymous 关闭,与上面列表截图一致):

# 带 token 调用(推荐使用请求头)
curl 'http://<服务器地址>/api/service/%E8%AE%BE%E5%A4%87%E7%8A%B6%E6%80%81%E6%9F%A5%E8%AF%A2?equipmentCode=EQ-001' \
  -H 'Authorization: Bearer <access_token>'
# 返回 HTTP 200
# {"caller":"超级管理员","method":"GET","equipmentCode":"EQ-001","runningStatus":"运行中"}

# POST JSON 请求体
curl -X POST 'http://<服务器地址>/api/service/%E8%AE%BE%E5%A4%87%E7%8A%B6%E6%80%81%E6%9F%A5%E8%AF%A2' \
  -H 'Authorization: Bearer <access_token>' -H 'Content-Type: application/json' \
  -d '{"equipmentCode":"EQ-002"}'

# 不带 token,且服务未开启 Enable Anonymous
curl 'http://<服务器地址>/api/service/%E8%AE%BE%E5%A4%87%E7%8A%B6%E6%80%81%E6%9F%A5%E8%AF%A2?equipmentCode=EQ-001'
# 返回 HTTP 500
# {"msg":"ErrorCode: 11004, ErrorMsg: AnonymousInvocation","errorCode":11004}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

平台内部(其他动态逻辑、插件)不再通过动态服务互相调用;旧版的 DynamicServiceConsumerService、服务发现、jvm:// 调用方式 1.0 起已移除(插件之间可改用 动态 RPC)。

# 错误码

平台拦截请求时,响应体格式为 {"msg": "...", "errorCode": <错误码>}:

errorCode 名称 场景
11001 ProviderNotFound 找不到该名称的服务
11003 ProviderInactivated 服务未激活(Active 关闭)
11004 AnonymousInvocation 未登录调用,且服务未开启 Enable Anonymous

平台按上表从上到下依次检查,例如服务未激活时,不论是否登录都返回 11003。逻辑执行中抛出普通异常时,响应体为 {"msg": "<异常信息>"};抛出的是平台异常(例如逻辑限流时的 1007 RateLimitExceed)时,同样带 errorCode。

HTTP 状态码

当前版本中,以上所有错误的 HTTP 状态码都是 500,不区分“找不到”“未授权”等情况。请按响应体中的 errorCode 判断错误类型,不要依赖 HTTP 状态码。

# 配置的生效时间

平台会缓存动态服务的定义(Active、Enable Anonymous、Enable Log、Body Type 等配置)。按设计,修改服务后缓存应当立即失效;但当前版本有一个已知缺陷:插件导入完成时平台会清掉缓存的变更监听登记,而动态服务仍在使用导入前拿到的那个缓存,此后修改配置不会让它失效,只能等它自然过期。这个缓存在连续 10 分钟没有被调用后才会过期,所以修改这些配置后,只要服务一直有人调用,新配置就不会生效。实测:修改 Enable Anonymous、Active 后立即调用,仍按旧配置执行;停止调用 10 分钟后再调用,新配置才生效。

注意

这意味着关闭 Enable Anonymous 或 Active 并不能立即阻止调用。需要紧急停用某个服务时,可以把它的逻辑改为直接返回错误(逻辑修改立即生效),例如 return [status: 403, body: [message: '服务已停用']]。

逻辑代码的修改不受这个缓存影响,保存后下一次调用即生效。

# 执行记录

对开启了 Enable Log 的服务,每次进入逻辑执行的调用都会生成一条执行记录(被 错误码 中的检查拒绝的调用不记录),在 执行记录 > 服务 中查看,也可以在动态服务列表中点击 Exec Records 列的按钮查看某个服务的记录。

动态服务执行记录

截图中的记录按时间倒序排列:下面两条来自上文 调用方式与认证 中带 token 的 GET、POST 调用;最上面一条是一次不带 equipmentCode 的调用,逻辑返回了 400,但逻辑本身执行成功,所以状态为 Success。未登录的那次调用被 11004 拒绝,没有记录。

执行记录的列较多,界面上要横向滚动才能看到 执行结果、执行日志、错误堆栈 等列(截图为了一屏显示全部列,加宽了窗口并截短了长文本列)。错误堆栈 列点其中的按钮查看完整内容。执行结果 以 JSON 保存,其中的中文被转义成 \uXXXX 的形式。当前版本点击行末的 详情,弹窗会一直处于加载状态(平台没有为动态服务执行记录配置详情表单),这是当前版本的界面问题。

列名 说明
服务提供者 被调用的动态服务
状态 Success 或 Failed
Exec user 调用者,未登录调用时为匿名身份
开始时间 开始执行的时间
结束时间 执行结束的时间
执行参数 请求的方法、URL 参数和请求体
执行结果 逻辑的返回值
执行日志 逻辑中通过 log 打印的日志
错误堆栈 出错时的异常信息

注意

通过 URL 参数传递的 access_token 会原样记录在“执行参数”中。调用开启了 Enable Log 的服务时,请使用 Authorization 请求头传递 token。

Last Updated: 2026/9/24 14:27:35