SettingDefinition 数据库存储实现说明
概述
实现了类似 ABP Framework 的设置定义(SettingDefinition)数据库存储功能,将设置定义从内存存储改为数据库存储,支持在线修改配置。
架构设计
核心概念
-
SettingDefinition(设置定义)
- 存储设置的元数据:名称、默认值、显示名称、描述等
- 支持数据类型、分组、排序等 UI 展示属性
- 支持加密标记(IsEncrypted)
- 存储在数据库表
identity_setting_definitions
-
Setting(设置值)
- 存储实际的值(可覆盖默认值)
- 支持多层级:系统(S)、租户(T)、用户(U)、配置(C)
- 存储在数据库表
identity_settings
-
ISettingDefinitionProvider(设置定义提供者)
- 代码中定义设置的入口
- 首次启动时自动同步到数据库
- 后续修改以数据库为准
数据流程
应用启动
↓
SettingDefinitionManager.InitializeAsync()
↓
从数据库加载 SettingDefinition
↓
如果数据库为空,从 Provider 同步到数据库
↓
获取设置值时:
1. 从 Setting 表按优先级查找(U > T > S > C)
2. 如果都没有,返回 SettingDefinition.DefaultValue
主要变更
1. SettingDefinition 实体升级
新增字段:
IsEncrypted: 是否加密存储DataType: 数据类型(string, bool, int, decimal, enum 等)Options: 可选项(JSON 格式,用于下拉框等)Order: 排序GroupName: 分组名称(用于 UI 分组显示)
修改:
Name和DefaultValue从只读属性改为可读写- 增加唯一索引
ix_setting_definition_name
2. SettingDefinitionManager
之前:纯内存存储,从 Provider 加载后保存在 Dictionary 中
现在:
- 从数据库加载设置定义
- 首次启动时自动将 Provider 中的定义同步到数据库
- 支持缓存机制(首次加载后缓存在内存)
- 异步操作(所有方法改为 async/await)
3. SettingDefinitionService
新增应用服务,提供 CRUD 操作:
CreateAsync: 创建设置定义UpdateAsync: 更新设置定义GetByNameAsync: 根据名称获取GetVisibleDefinitionsAsync: 获取对客户端可见的定义
4. 数据库迁移
提供两种数据库的迁移脚本:
- SQL Server:
20260330_SettingDefinition_Upgrade.sql - MySQL:
20260330_SettingDefinition_Upgrade_MySQL.sql
API 接口说明
SettingDefinitionController(设置定义管理)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/identity/settingdefinition/visible | 获取所有可见的设置定义 |
| GET | /api/identity/settingdefinition/by-name/{name} | 根据名称获取设置定义 |
| GET | /api/identity/settingdefinition | 获取设置定义列表(分页) |
| GET | /api/identity/settingdefinition/{id} | 根据 ID 获取设置定义 |
| POST | /api/identity/settingdefinition | 创建设置定义 |
| PUT | /api/identity/settingdefinition/{id} | 更新设置定义 |
| DELETE | /api/identity/settingdefinition/{id} | 删除设置定义 |
SettingController(设置值管理)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/identity/setting/u/key_values | 批量设置用户配置 |
| GET | /api/identity/setting/u/key/{key}/{userId} | 获取指定用户的配置值 |
| GET | /api/identity/setting/u/keys/{userId} | 批量获取用户配置值 |
| GET | /api/identity/setting/u/key/{key} | 获取当前用户的配置值 |
| GET | /api/identity/setting/u/keys | 批量获取当前用户配置值 |
使用示例
定义设置(SettingDefinitionProvider)
public class AccountSettingDefinitionProvider : ISettingDefinitionProvider
{
public void Define(ISettingDefinitionContext context)
{
context.Add(new SettingDefinition(
name: "Account.IsSelfRegistrationEnabled",
defaultValue: "true",
displayName: "是否允许自助注册",
description: "控制是否允许用户自助注册"
)
.WithDataType("bool") // 数据类型
.WithGroupName("账户设置") // 分组
.WithOrder(1) // 排序
.WithVisibility(true) // 对客户端可见
.WithEncryption(false)); // 不加密
}
}
获取设置值
// 注入 ISettingManager
public class MyService
{
private readonly ISettingManager _settingManager;
public MyService(ISettingManager settingManager)
{
_settingManager = settingManager;
}
public async Task<bool> CheckSelfRegistration()
{
// 获取布尔值
return await _settingManager.IsTrueAsync("Account.IsSelfRegistrationEnabled");
}
public async Task<string> GetSmtpHost()
{
// 获取字符串值(系统级别)
return await _settingManager.GetOrNullAsync(
"MailKit.Host",
SettingProviderNameEnum.S
);
}
public async Task<string> GetUserSetting(Guid userId)
{
// 获取用户级别的设置
return await _settingManager.GetOrNullAsync(
"Some.UserSetting",
SettingProviderNameEnum.U,
userId.ToString()
);
}
}
修改设置值
// 通过 SettingService 修改
public class SettingAppService
{
private readonly ISettingService _settingService;
public async Task UpdateSmtpHost(string newHost)
{
await _settingService.SetKeyValuesAsync(new SettingCreateUpdateReq
{
Name = "MailKit.Host",
Value = newHost,
ProviderName = SettingProviderNameEnum.S,
ProviderKey = null
});
}
}
前端展示设置
// 获取所有可见的设置定义(用于前端动态渲染表单)
var definitions = await settingDefinitionService.GetVisibleDefinitionsAsync();
// 返回结果示例:
[
{
"name": "Account.IsSelfRegistrationEnabled",
"displayName": "是否允许自助注册",
"description": "控制是否允许用户自助注册",
"defaultValue": "true",
"dataType": "bool",
"groupName": "账户设置",
"order": 1,
"isEncrypted": false
},
{
"name": "MailKit.Host",
"displayName": "邮件服务器地址",
"dataType": "string",
"groupName": "邮件设置",
"order": 2
}
]
前端渲染建议
根据 DataType 字段渲染不同的输入控件:
| DataType | 推荐控件 | 说明 |
|---|---|---|
| string | 文本框 | 单行文本输入 |
| string (with Options) | 下拉框 | Options 为 JSON 数组 |
| bool | 开关/复选框 | true/false |
| int | 数字输入框 | 整数 |
| decimal | 数字输入框 | 小数 |
| enum | 下拉框/单选框 | Options 为枚举值 |
Options JSON 格式示例:
[
{"label": "选项 1", "value": "option1"},
{"label": "选项 2", "value": "option2"}
]
加密设置
对于敏感信息(如密码、密钥),可以标记为加密:
new SettingDefinition(
"Smtp.Password",
defaultValue: "",
displayName: "邮件服务器密码"
)
.WithDataType("string")
.WithEncryption(true) // 标记为加密
.WithGroupName("邮件设置");
获取加密设置时,系统会自动解密(需要配合加密中间件)。
注意事项
-
首次部署:
- 执行数据库迁移脚本
- 应用启动时会自动将 Provider 中的定义同步到数据库
-
后续修改:
- 重要:Provider 中的定义只作为"种子数据"
- 一旦数据库中有了记录,就以数据库为准
- 修改 Provider 中的定义不会覆盖数据库中的值
- 如需修改,应该通过
SettingDefinitionService在线修改,或直接修改数据库
-
设计理念:
- 开发阶段:可以在数据库中删除记录,让代码重新同步
- 生产环境:数据库中的配置是权威的,代码不会覆盖
- 这样可以保证用户在线修改的配置不会被代码更新重置
-
性能优化:
- SettingDefinitionManager 使用缓存机制
- 首次加载后,后续操作从内存缓存读取
- 调用
ClearCache()可强制重新加载
-
多租户:
- SettingDefinition 是全局的,不支持租户隔离
- Setting(设置值)支持租户隔离(通过 TenantId)
文件清单
实体和配置
SettingDefinition.cs- 设置定义实体(升级)SettingDefinitionConfiguration.cs- 实体配置(升级)
领域层
SettingDefinitionManager.cs- 设置定义管理器(升级)ISettingDefinitionProvider.cs- 定义提供者接口(不变)SettingDefinitionContext.cs- 定义上下文(不变)
应用层
SettingDefinitionService.cs- 设置定义服务(新增)SettingDefinitionDto.cs- DTO(新增)SettingDefinitionCreateUpdateReq.cs- 创建/更新请求(新增)SettingDefinitionQuery.cs- 查询条件(新增)ISettingDefinitionService.cs- 服务接口(新增)
接口层
SettingDefinitionController.cs- 设置定义控制器(新增)SettingController.cs- 设置值控制器(已存在)
启动配置
IdentityInfrastructureModuleStartup.cs- 模块启动配置(升级)
数据迁移
20260330_SettingDefinition_Upgrade.sql- SQL Server 迁移脚本20260330_SettingDefinition_Upgrade_MySQL.sql- MySQL 迁移脚本
SettingDefinitionProvider 示例
AccountSettingDefinitionProvider.cs- 账户设置提供者(升级)CmsKitSettingDefinitionProvider.cs- CMS 设置提供者(升级)
兼容性
- ✅ 向后兼容:现有的 Setting 表和使用方式不变
- ✅ 渐进式迁移:可以逐步将配置从 appsettings.json 迁移到数据库
- ✅ 多数据源支持:仍然支持从配置文件、数据库等多来源获取设置值
未来扩展
- 设置验证:增加 DataType 的验证逻辑
- 本地化支持:DisplayName 和 Description 支持多语言
- 设置依赖:支持设置之间的依赖关系
- 审计日志:记录设置值的变更历史
- 批量操作:支持批量导入/导出设置