# FAQ 常见问题

# 目标读者

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

# 为什么枚举字段被系统识别为对象字段?

请确认枚举字段的包名称中,包含 enums 部分,比如

package tech.muyan.dynamic.task.enums  // --> 包名称中含 enums 部分,可以正确识别

Enum DynamicTaskType {
  RUN_AT_STARTUP('RUN_AT_STARTUP'),
  CRON_TASK('CRON_TASK'),
  SCHEDULE_TASK('SCHEDULE_TASK'),

  ;

  String value
}
1
2
3
4
5
6
7
8
9
10
11

可以被正常识别,而下述枚举会被识别为对象类型

package tech.muyan.dynamic.task // --> 包名称中不含 enums 部分,无法正确识别

enum DynamicTaskType {
  RUN_AT_STARTUP('RUN_AT_STARTUP'),
  CRON_TASK('CRON_TASK'),
  SCHEDULE_TASK('SCHEDULE_TASK'),

  ;

  String value
}
1
2
3
4
5
6
7
8
9
10
11

# 如何在 websocket handler 代码中手动注入 bean 定义

  1. 在应用的启用入口文件 tech/muyan/Application.groovy 文件中的 registerWebSocketHandlers 方法中, 注册新创建的 WebSocket handler, 并使用 @bean 注解 将其注册为 bean。
  2. 使用 WebSocketInitHelper.createWebSocketHandler(applicationContext, implementation) 来将实现注册为 WebSocket 的 handler。

提示

如下第 5 行注释表述了使 websocket handler 具备 bean 注入能力的具体代码调用

public static PerConnectionWebSocketHandler createWebSocketHandler(
  ApplicationContext applicationContext,
  Class<? extends WebSocketHandler> handlerType) {
  PerConnectionWebSocketHandler handler = new PerConnectionWebSocketHandler(handlerType);
  // 下一句的调用让具体 WebSocketHandler 具备了使用 @Autowired 注入 bean 的能力 
  handler.setBeanFactory(applicationContext.getAutowireCapableBeanFactory());
  return handler;
}
1
2
3
4
5
6
7
8
  1. 在对应的实现中, 使用 @Autowired 注解来注入系统中定义的 bean, 具体可参考现有的 messageSummaryWebSocketHandlerfileUploadWebSocketHandler 的实现

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

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

参考如下的代码, 从 bean 的注册表中根据名称手动查找到 service 定义, 其中 authorityService 是 service 的名称, 对应于 Service 定义的 class 名称, 首字母小写.

  import tech.muyan.BeanHelper
  import grails.core.GrailsApplication

  GrailsApplication grailsApplication = (application as GrailsApplication)
  AuthorityService authorityService = BeanHelper.getBean(grailsapplication, "authorityService")  
1
2
3
4
5

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

请确认, 作为过滤器条件的字段, 包含在表单字段中, 如果作为过滤器条件的字段, 没有返回前台, 则该字段无法作为过滤器条件使用.

# 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) 来设置缓存数据,并返回当前已经保存的数据,
  • RegistryHelper.memoryRemove(key) 来移除缓存数据。
注意 上述方法共享的全局数据保存在内存中,故只支持单服务器实例内的数据共享,不支持在多服务器实例之间进行数据共享。

# 用户打开 Dashboard, Widget 中报错

报错截图如下:

报错截图

先确认:

  1. 请确认不是在 windows 系统下打包的 jar 文件,当前 grails 有 bug 导致 windows 下生成的后端部署包中的 gson 文件编译生成的 class 文件名称错误,从而导致所有的 枚举字段传递到前端的值不对,

解决方法:

  1. 从 Github action 页面找到待部署的 commit SHA 对应的 action 运行, 直接下载 action 运行生成的部署包
  2. 在 MacOS 或者 Linux 下打包后端应用

# 后端启动正常但前端连接显示 401 无法连接

请检查 application.yml 文件是否正确,常见的问题包括因为修改导致的缩进或其他格式 问题,进而导致相关配置没有被系统正确读入。可以尝试将 application.yml 文件恢复为 template repository 中的或其他人的原始版本进行尝试

# 系统前后端上线后,前端请求后端接口报错或者用户无法登陆,白屏幕等

请尝试如下操作

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

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

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

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

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

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

如下是一个示例配置:

class Feedback implements Serializable, MultiTenant<Feedback>,
  Auditable, Stampable<Feedback>, HasComment<Feedback> {
  List<StorageFieldValue> attachments
  static hasMany = [attachments: StorageFieldValue]
}
1
2
3
4
5

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

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

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

class DynamicMenu implements MultiTenant<DynamicMenu>,
  Auditable, Stampable<DynamicMenu>, Serializable {
  // 其他字段省略
  // 在 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 接口

  1. 实现接口 tech.muyan.websocket.WebSocketService
  2. 实现方法 abstract String getWebSocketPath() 返回 websocket 的路径
  3. 实现方法 void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception 处理 websocket 消息

# 如何定义 Wizard 第一个步骤的字段的默认值

最后更新: 2024/5/26 15:43:38