# 系统配置 开发
系统配置使用 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
# 修订历史
修改 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); 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);
2
3
4
5
6
7
8
9
10
11
12
13
14
使用方式如下:
在插件的 Service 或其他 Spring Bean 中,直接注入
tech.muyan.api.config.DynamicConfigService。在动态逻辑中,可以通过静态字段
tech.muyan.ConfigHelper.dynamicConfigService获取服务:
import tech.muyan.ConfigHelper
Integer interval = ConfigHelper.dynamicConfigService
.getByKeyOrDefault("websocket.heartbeatInterval", 20)
2
3
4
- 插件组件(实现
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
}
2
3
4
5
6