# FAQ 常见问题

# 目标读者

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

# 如何在客制化代码中手动注入 service 定义

在项目实施过程中,为了代码重用,可能将某些通用逻辑以 service 形式在系统中定义,在客制化的 groovy 代码中,可以方便的查找到 service 定义并调用其相关方法。

参考如下的代码,从 bean 的注册表中按名称或类型查找 service 并调用。按名称查找时,名称是 Service 类名首字母小写,例如 AuthorityService 对应 authorityService。

import tech.muyan.BeanHelper
import tech.muyan.security.AuthorityService

// 按名称查找
AuthorityService authorityService = BeanHelper.getBean("authorityService")

// 或按类型查找
AuthorityService sameService = BeanHelper.getBean(AuthorityService)
1
2
3
4
5
6
7
8

# 表单中定义了默认过滤器,但是没有生效

当前版本的前端不读取动态过滤器(DynamicFilter),列表页面不会显示任何过滤器,标记为默认(isDefault)的过滤器也不会自动应用,详见 动态过滤。

如果想让列表默认带上过滤条件,请在表单 extInfo 中设置 listForm.searchConditions,见 默认过滤条件。

# extInfo 字段无法成功通过 csv 导入

请确认 extInfo 字段(在数据库中以 JSON 类型存储)的内容包含双引号(")时,在导入的 CSV 中使用两个双引号("")转义,而不是使用反斜杠(\)转义。

# 在 Dynamic Logic 执行间共享全局配置、连接、或进行数据缓存

在某些场景下,可能需要在多次 Dynamic Logic 执行中共享一些数据,典型场景比如:

  1. 保存并共享一些计算较耗费资源的缓存数据
  2. 需要在全局共享的一些配置或资源,如第三方系统的有状态连接,Kafka、Message Queue 等连接的管理
  3. 不同 Dynamic Logic 的执行之间,可能存在业务逻辑上的先后顺序,且无法避免的,可以将之前步骤的运行结果进行缓存。

当前系统提供了 tech.muyan.helper.RegistryHelper 类来进行全局共享数据的管理,可以通过

  • RegistryHelper.memoryGet(key) 来获取缓存数据,
  • RegistryHelper.memoryPut(key, value) 来设置缓存数据,返回该 key 之前保存的值(之前没有则返回 null),
  • RegistryHelper.memoryRemove(key) 来移除缓存数据。
注意 上述方法共享的全局数据保存在内存中,故只支持单服务器实例内的数据共享,不支持在多服务器实例之间进行数据共享。

# 后端启动正常,但前端无法登录或接口返回 401

平台的配置写在 docker-compose.yml 的环境变量里,请检查:

  1. runtime/proxy/conf.d/default.conf 中 X-Muyan-Tenant 的值是否与后端的 TENANT_ID 一致。不一致时后端返回 Tenant xxx not found in system,见 更改默认租户。
  2. 修改或新增 JWT_SECRET 后,之前签发的令牌全部失效,需要重新登录;如果浏览器仍带着旧令牌,请按下一条清理浏览器缓存。JWT_SECRET 至少 32 字节,太短时后端无法启动。

# 系统前后端上线后,前端请求后端接口报错、用户无法登录或白屏

请尝试如下操作

  1. 在 web 服务器端,删除或者强制刷新 nginx 缓存
  2. 在浏览器端,清空前端 localStorage 中的条目
  3. 在浏览器端,删除或者强制刷新浏览器缓存

# Domain 中包含可空的 primitive 类型字段,其值为空时对象保存失败

请在 Domain 定义中,将可空的 primitive 类型字段定义为对应的包装类型,如 int 改为 Integer,double 改为 Double,boolean 改为 Boolean 等。

# Domain 中包含的字段在前端没有显示

请检查该字段是否在 Domain 定义的 constraints 中有定义

static constraints = {
  attachments nullable: true
}
1
2
3

如下是一个示例配置:

class Feedback implements Serializable, MultiTenant<Feedback>,
  Auditable, HasComment<Feedback> {
  // 其他字段省略
  List<StorageFieldValue> attachments

  static hasMany = [attachments: StorageFieldValue]

  static constraints = {
    // 其他约束省略
    attachments nullable: true
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 在 One to many 关联关系中,无法保存关联关系

一个可用的定义可参考 DynamicMenu.groovy,其中定义了指向自身的 parent 字段、以及反向引用的 children 字段

具体 GORM 的文档可参考 GORM Associations (opens new window)

class DynamicMenu implements MultiTenant<DynamicMenu>,
  Auditable, Serializable, RenderableObject {
  // 其他字段省略
  // 在 Many 端指向 One 端的字段
  DynamicMenu parent
  
  // 在 One 端指向 Many 端的字段
  static hasMany = [children: DynamicMenu]
  // 请注意不能显式使用 List<DynamicMenu> children 形式定义 children 字段
  // 否则会导致 GORM 无法保存 parent 字段

  static constraints = {
    // 其他约束省略
    parent nullable: true
    children nullable: true
  }
  
  static mapping = {
    // 不需要定义 children 字段的 mapping,该字段只是作为反向引用使用
    parent index: 'dynamic_menu_parent_idx'
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 如何定义新的 Websocket 接口

当前版本平台只提供一个固定的 WebSocket 入口,前端经 nginx 连接的地址是 /api/websocket/route,不能再注册新的 WebSocket 路径。要处理新的消息类型,在插件中继承 tech.muyan.api.websocket.MuyanWebSocketComponent,按 topic 接收和推送消息,见 插件开发 · WebSocket。

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