# 系统配置 开发

系统配置使用 DynamicConfig 对象定义,用于保存可在运行时修改的配置项,例如附件存储方式、WebSocket 心跳间隔等。插件和动态逻辑都可以读取这些配置。

# 目标读者

本文档的目标读者为:本系统的开发、实施、运维人员,或需要进行系统配置的高级用户。

# 配置定义

在菜单 开发配置 > 系统配置 中维护配置,配置以树形结构组织。相关字段如下:

字段 描述
name 配置的名称,唯一,创建后不可修改
key 在前后端 API 中获取配置时使用的键,唯一,创建后不可修改
value 配置的值,以文本保存
parent 上级配置,用于把相关的配置组织成一棵树
description 配置描述
modifyRemark 修改备注
displaySequence 在配置树中的显示顺序。新建没有上级的配置时如果不填,平台自动取所有顶层配置的当前最大值加 10;新建有上级的配置时必须填写,见下方警告
allowPublicAccess 是否允许未登录用户通过前端接口读取该配置,默认 false
icon 在配置树上显示的图标
isSystem 是否为系统内置配置。非开发环境下,系统内置配置不能删除

CSV 种子数据的表头如下:

name,key(*),value,parent.key,description,modifyRemark,displaySequence,allowPublicAccess,icon,isSystem
1

# 修订历史

修改 value、allowPublicAccess、description、modifyRemark 时,平台会保存修订历史。在编辑表单的历史版本中可以比较版本差异,或把某个历史版本设置为最新版本。

# 新建与删除

当前版本新建下级配置、删除配置会失败

新建时的显示顺序、删除前的检查由两个内置的对象客制化(DynamicConfigBeforeCreate、DynamicConfigBeforeDelete)实现,它们在当前版本中都有缺陷,报错均为 groovy.lang.MissingPropertyException: No such property: children for class: tech.muyan.dynamic.config.DynamicConfig:

  • 新建有上级的配置:没有填写 displaySequence 时保存失败。新建表单中有"上级"字段,但没有显示顺序字段,所以在界面上无法新建下级配置。请通过 CSV 种子数据或数据 API(POST /data/DynamicConfig;本页接口路径都省略了 /api 前缀,见 地址前缀)新建,并显式填写 displaySequence。新建没有上级的配置不受影响。
  • 删除配置:无论是否有下级配置,删除任何配置都会失败。确实需要删除时,可以在 开发定制 > 对象客制化 中找到 DynamicConfigBeforeDelete,在编辑表单中临时关闭"是否生效中"后再删除,删完立即打开(实测关闭后立即生效)。关闭期间"有下级配置的配置不能删除"的检查也不会执行,删除前请自己确认该配置没有下级配置。

设计上的规则是:有下级配置的配置不能删除,需要先删除所有下级配置;非开发环境下,系统内置(isSystem)的配置不能删除。删除配置是物理删除,回收站 > 系统配置 菜单中不会出现被删除的配置。

# 环境变量优先

读取配置时,平台先查找同名的操作系统环境变量,存在时直接使用环境变量的值,不再读取数据库中的配置;环境变量不存在时,才使用 DynamicConfig 中的值。

环境变量名由配置 key 把所有 . 替换为 _ 得到,不转换大小写。例如配置 key attachment.storageEngine 对应的环境变量是 attachment_storageEngine。

提示

  • 配置值读取后会缓存在内存中。在界面上修改配置后,缓存会立即更新为数据库中的新值,即使该 key 设置了环境变量也是如此,直到后端重启后才会重新以环境变量为准。
  • 前端接口 GET /config/{key} 同样遵循环境变量优先的规则,但前提是数据库中存在该 key 的配置记录。

# 前台获取配置 API

前端通过 @muyantech/frontend-lib 提供的 getConfig 获取配置,它调用接口 GET /config/{key},返回 { key, value }:

import { getConfig } from "@muyantech/frontend-lib";

// 调用 GET /api/config/{key},返回 { key, value },value 为字符串
// Calls GET /api/config/{key} and resolves to { key, value }; value is a string
const config: any = await getConfig('websocket.heartbeatInterval');
const heartbeatInterval = Number(config?.value ?? 20);
1
2
3
4
5

接口规则:

  • 数据库中不存在该 key 时,返回空对象 {}。
  • 未登录用户只能读取 allowPublicAccess 为 true 的配置,否则返回 403。
  • 已登录用户可以读取任意配置。敏感信息(密钥、密码)请不要只依赖该开关保护,可以改为通过环境变量提供。

另有 GET /config/system,无需登录,返回前端启动所需的系统信息(例如附件是否启用预签名上传)。

# 后台获取配置 API

后台的配置服务接口为 tech.muyan.api.config.DynamicConfigService,提供以下方法:

// 接口 tech.muyan.api.config.DynamicConfigService
// Interface tech.muyan.api.config.DynamicConfigService

// 按 key 获取配置值(字符串),不存在时返回 null
String getByKey(String key);

// 按 key 获取配置值并转换为指定类型,不存在时返回 null
<T> T getByKey(String key, Class<T> typeClass);

// 按 key 获取配置值并转换为默认值的类型,不存在时返回 defaultValue
<T> T getByKeyOrDefault(String key, T defaultValue);

// 监听某个 key 的变更,回调参数为 (修改前的值, 修改后的值),返回值用于取消监听
ListenerUnregister register(String key, DynamicConfigChangeEventListener listener);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

使用方式如下:

  1. 在插件的 Service 或其他 Spring Bean 中,直接注入 tech.muyan.api.config.DynamicConfigService。

  2. 在动态逻辑中,可以通过静态字段 tech.muyan.ConfigHelper.dynamicConfigService 获取服务:

import tech.muyan.ConfigHelper

Integer interval = ConfigHelper.dynamicConfigService
  .getByKeyOrDefault("websocket.heartbeatInterval", 20)
1
2
3
4
  1. 插件组件(实现 tech.muyan.api.MuyanPlatformComponent 的类)的字段上可以使用注解 @tech.muyan.api.annotations.DynamicConfig("配置 key")。插件加载时,平台通过该字段的公开 setter 写入配置值,配置变更后自动再次调用 setter 更新:
import tech.muyan.api.annotations.DynamicConfig

class HeartbeatSettings implements tech.muyan.api.MuyanPlatformComponent {
  @DynamicConfig("websocket.heartbeatInterval")
  Integer heartbeatInterval
}
1
2
3
4
5
6
Last Updated: 2026/9/24 14:27:35