跳到主要内容

Identity.Infrastructure 开发指南

Infrastructure 层为 Identity 及所有业务模块提供一组可复用的横切能力,而非某个特定业务功能。它解决的是"文件怎么存、配置怎么读、附件怎么挂、字典怎么管"这类通用问题,任何模块(CmsKit、Platform 等)都可以直接消费这些服务。

本指南聚焦功能与用法,不罗列实体字段。涉及到的实体仅用于帮助理解数据模型。

能力一句话说明主要服务接口
文件存储 Files统一文件上传/下载,支持本地、七牛、Sosoos、CloudFlare R2,按内容哈希秒传去重IFileService / IFileManager / IAvatarService
设置系统 Settings分层(用户/租户/系统/角色/配置)键值配置,运行时可覆盖ISettingManager / ISettingService
设置定义 SettingDefinitions配置的元 Schema,驱动前端动态表单ISettingDefinitionManager / ISettingDefinitionService
通用附件 CommonAttachments把文件挂载到任意业务实体(文章封面、内容图等),带类型与权限校验ICommonAttachmentService / ICommonAttachmentManager
数据字典 BaseType / BaseItem通用枚举字典(性别、状态码等),前端下拉数据源IBaseTypeService / IBaseItemService

1. 文件存储(Files)

能力概述

文件上传后生成 KitFile 记录(存储路径、大小、后缀、哈希等),并通过 KitFileMetaMD5 内容哈希聚合同一份文件在不同存储后端(本地 / R2 / 七牛)的多条记录,实现秒传去重:相同内容第二次上传不会重复落盘,只新增一条引用。

支持的存储后端由 FileUploadType 枚举描述:Local / Qiniu / Sosoos / Aliyun / Tencent / CloudFlare / Aws,通过 IFileStorageOptionsResolver 解析 ServiceName 选择具体实现(LocalFileManagerQiniuManagerSosoosManagerCloudFlareR2Manager,均以 keyed 方式注册)。

核心服务

  • IFileService:上传与文件 CRUD。UploadAsync 有多种重载——IFormFile、字节数组、Stream、远程 URL;也支持批量上传(受 NumLimit / MaxFileSize 约束)。
  • IFileManager:根据 PathFileMetaId 解析可访问的完整 Url,支持批量回填、缓存与头像 URL 解析(FillAvatarUrlsAsync / BindFileUrls)。业务表只存 PathFileMetaId,展示时统一通过它取 URL。
  • IAvatarService:基于 SkiaSharp 生成文字头像(GenerateAvatar)、按名字复用同一头像(CreateAvatarUrlByNameAsync,MD5 命名避免重复)、随机头像(GetRandomAvatar)。

HTTP API(FileController/api/identity/file

方法路由说明
POST/uploads[FromForm] List<IFormFile>多文件上传
POST/uploadIFormFile file单文件上传
POST/upload-from-urlstring url按远程 URL 抓取上传
GET/{id:long}文件详情(含 Url
GET/分页列表(Name/Type/IsFileExist 过滤)
PUT/{id:long}仅改文件名
DELETE/{id:long}删除
PUT/check_all_file_exists校验本地文件实际是否存在
GET/check_file_exists/{id:long}校验单个文件
GET/create_avatar?name=AllowAnonymous返回 PNG 字节流头像
GET/create_avatar_url_by_name?name=生成并上传头像,返回 FileDto
GET/get_random_avatarAllowAnonymous随机头像

配置(FileStorage 节)

{
"FileStorage": {
"MaxFileSize": 10485760,
"NumLimit": 10,
"Include": "jpg,jpeg,png,gif,mp4,mp3,pdf",
"Exclude": "exe,bat",
"ServiceName": "CloudFlareR2",
"LocalFile": { "RootPath": "wwwroot/files", "PrefixPath": "files", "Host": "" },
"CloudflareR2": {
"AccountId": "", "AccessKey": "", "SecretKey": "",
"Bucket": "", "PrefixPath": "files", "Host": "https://cdn.example.com", "UseHttps": true
}
}
}

运行时覆盖FileStorageOptionsResolver 会合并 Setting(键 FileStorage.Config,JSON)中的值到上述配置,变更 R2 凭据时自动刷新 DI 中的单例客户端。即无需改 appsettings 即可热更新存储配置。

开发示例

// 1) 上传(例如 TTS 音频,按业务前缀隔离)
var file = await _fileService.UploadAsync(audioBytes, "speech.wav", key: 0, prefixPath: "tts");
// file.Path / file.Url / file.Id 可存入业务表

// 2) 展示时解析完整 URL
var url = await _fileManager.GetFileUrlAsync(file.Path);
# 单文件上传
curl -X POST https://localhost:7002/kit_api/api/identity/file/upload \
-F "file=@/path/to/cover.png"

2. 设置系统(Settings)

分层 Provider 模型

设置值(Setting)按来源 Provider分层,取值优先级从高到低为:

用户 (U) > 租户 (T) > 系统 (S) > 配置文件 (C)

全部未命中时回落到 SettingDefinition.DefaultValue。各层由对应的 SettingValueProvider 实现:

  • UserSettingValueProvider(U,按 ProviderKey=用户Id
  • TenantSettingValueProvider(T)
  • SystemSettingValueProvider(S)
  • ConfigurationSettingValueProvider(C,读 IConfiguration,键名 . 自动转 :

ISettingManager 按优先级聚合取值,启动时还会把 appsettings 中已定义但库中缺失的 S 级配置种子化(标记 BuiltInParams,禁止删除/改键)。

核心服务

  • ISettingManager:读配置的主力。GetOrNullAsync(name) / GetOrNullAsync<T>()GetSystemBoolAsync / GetSystemIntAsync / GetSystemStringListAsyncIsTrueAsyncFindAsync
  • ISettingService:写配置与 CRUD。SetKeyValuesAsync 不存在则建、存在则更新(JSON 类型会做语法与字段校验),并保护内置参数不被覆盖。

HTTP API

用户侧(SettingController/api/identity/setting

方法路由说明
POST/u/key_valuesIDictionary<string,string>批量设置当前用户配置
GET/u/key/{key}取当前用户某键
GET/u/keys?keys[]=批量取当前用户
GET/u/key/{key}/{userId}取指定用户某键(仅可见定义)
GET/u/keys/{userId}?keys[]=批量取指定用户
GET/visible/grouped按 Group/SubGroup 分组返回可见定义(前端动态表单用)

管理侧(SettingAdminController/api/identity/setting/admin,需 Identity.Settings.*

方法路由权限
GET/Settings.GetList
GET/{id:guid}Settings.GetList
POST/Settings.Create
PUT/{id:guid}Settings.Update
DELETE/{id:guid}Settings.Delete
POST/{id:guid}/resetSettings.Delete(回到默认值)
GET/json-fields?name=&providerName=&providerKey=把 JSON 设置展平为字段列表
PUT/json-fieldUpdateJsonFieldReq按路径(如 CloudflareR2.Host)更新 JSON 内单字段

开发示例

// 读取系统级配置(类型安全)
var host = await _settingManager.GetOrNullAsync("MailKitOptions.Host", SettingProviderNameEnum.S);
var captchaOn = await _settingManager.GetSystemBoolAsync("Captcha.Enabled");

// 写入系统级配置
await _settingService.SetKeyValuesAsync(new SettingCreateUpdateReq
{
Name = "MailKitOptions.Host",
Value = "smtp.example.com",
ProviderName = SettingProviderNameEnum.S
});

常用定义名见 Runtime/IdentitySettingNames.cs,如 Captcha.EnabledMailKitOptions.HostFileStorage.ConfigMeiliSearch.HostRateLimit.Email.* 等。读取时把键中的 . 替换为 :IConfiguration


3. 设置定义(SettingDefinitions)

定义 vs 值

  • SettingDefinition = 配置的元 Schema:名称、默认值、数据类型、分组、是否加密、是否对客户端可见、JSON Schema。
  • Setting = 某个 Provider 下的实际值

这与 ABP 的 Setting 体系一致:定义描述"有哪些可配置项、长什么样",值记录"当前配了什么"。

注册定义

定义通过代码中的 ISettingDefinitionProvider 声明,首次启动同步入库(增量同步,不覆盖你在后台维护的展示文案);若 DataType=json 且含 JsonSchemaType,会自动从 CLR 类型递归生成 JSON Schema。

public class MySettingDefinitionProvider : ISettingDefinitionProvider
{
public void Define(ISettingDefinitionContext context)
{
context.Add(new SettingDefinition("My.Feature.Enabled", "false", "功能开关")
.WithDataType("bool")
.WithGroupName("高级")
.WithVisibility(true));
}
}
// 宿主启动时通过反射自动注册所有 ISettingDefinitionProvider

内置实现 IdentitySettingDefinitionProvider 已定义 OAuth、验证码、邮件、MeiliSearch、文件存储、限流等全部系统设置。

前端动态表单

拉取可见定义即可按 DataType / Options / GroupName 自动渲染输入控件:

curl https://localhost:7002/kit_api/api/identity/settingdefinition/visible

HTTP API(SettingDefinitionController/api/identity/settingdefinition

方法路由说明
GET/visibleAllowAnonymous可见定义列表
GET/by-name/{name}按名取定义
GET/分页列表(可按 Name/Group/SubGroup/IsVisible 过滤)
GET/{id:guid}详情
POST/新建
PUT/{id:guid}更新
DELETE/{id:guid}删除

4. 通用附件(CommonAttachments)

能力概述

通用附件把"文件"挂载到任意业务实体上(如文章封面、文章内容图、视频等)。它不存储文件本身,只存 FilePath / FileMetaId,文件实体仍在 KitFile 中——因此与 Files 能力完全打通:查询附件时自动回填 FileUrl。附件类型由扩展名推断(Image/Video/Audio/Document/Other)。

配置实体类型与访问校验

消费方需在模块启动时声明"哪些实体类型允许挂附件",以及分类规则与权限校验器:

services.Configure<CommonAttachmentOptions>(o =>
{
o.EntityTypes.Add(new CommonFileEntityTypeDefinition(nameof(Article))
.WithAccessValidator<ArticleAccessValidator>() // 上传前校验实体归属权限
.WithCategory("Article/Cover", AttachmentType.Image) // 封面:仅图片
.WithCategory("Article/Content", // 内容:多种类型
AttachmentType.Image, AttachmentType.Video,
AttachmentType.Audio, AttachmentType.Document, AttachmentType.Other));
});

上传/查询会经过 ICommonAttachmentValidator:校验实体类型是否允许挂附件、当前用户是否有权操作该实体,非法时抛 BusinessException / EntityCantHaveException

HTTP API(CommonAttachmentController/api/identity/common-attachment

方法路由说明
POST/AttachmentCreateInput新增附件(需先上传文件拿到 Path
DELETE/{id:guid}删除(非 Admin 仅能删自己创建的)
GET/AllowAnonymous分页列表(按 EntityId/EntityType/Category/AttachmentType 过滤,回填 FileUrl
GET/{entityId:guid}取某实体全部附件
PUT/check_all_file_exists批量校验文件存在性

典型使用流程

  1. 业务实体(如 Article)要支持附件时,在模块启动里 Configure<CommonAttachmentOptions> 注册 CommonFileEntityTypeDefinition 与访问校验器。
  2. 先走 Files 上传拿到 Path,再调用:
curl -X POST https://localhost:7002/kit_api/api/identity/common-attachment \
-H "Content-Type: application/json" \
-d '{
"entityId": "article-guid",
"entityType": "Article",
"category": "Article/Cover",
"attachmentType": 10,
"fileName": "cover.png",
"filePath": "files/202601/abc.png",
"fileSize": 12345
}'
  1. 查询时 GET /api/identity/common-attachment/{entityId},响应已带回 FileUrl

5. 数据字典(BaseType / BaseItem)

概念

通用数据字典,用于替代散落的硬编码枚举。关系为父子:

  • BaseType:字典分类(如"性别"),含唯一 TypeCode
  • BaseItem:字典项(如"男/女"),含 ItemCode / ItemName,可自引用形成层级,并支持 ExtraProperties(JSON 扩展字典)。

开发者通常把 BaseType 当作"枚举组"、BaseItem 当作"枚举值",并通过 TypeCode 查询(自动转换为 BaseTypeId)。

HTTP API(plat 域)

方法路由说明
GET/api/plat/type/listBaseTypeQuery 在 body)分类分页列表(IncludeBaseItems=true 一并加载子项)
GET/api/plat/type/{id}分类详情
POST/api/plat/type(需 Platform.BaseTypes新建分类
PUT/api/plat/type/{id}(需 Platform.BaseTypes更新
DELETE/api/plat/type/{id}(需 Platform.BaseTypes删除
GET/api/plat/item(AllowAnonymous)字典项分页列表
GET/api/plat/item/select(AllowAnonymous)返回 SelectMixedOption(Label=ItemName, Value=ItemCode),前端下拉用
POST/api/plat/item(需 Platform.BaseItems新建字典项
PUT/api/plat/item/{id}(需 Platform.BaseItems更新
DELETE/api/plat/item/{id}(需 Platform.BaseItems删除

前端下拉用法

# 拿"性别"下拉选项,Value 即为 ItemCode
curl "https://localhost:7002/kit_api/api/plat/item/select?typeCode=Gender"
# => [{ "label": "男", "value": "Male", "extraProperties": null }, ...]

模块注册要点

IdentityInfrastructureModuleStartup 在启动时完成:

  • 绑定 FileStorageOptionCloudflareR2Options 配置;
  • 以 keyed 方式注册 4 个文件上传实现(LocalFileManager / QiniuManager / SosoosManager / CloudFlareR2Manager);
  • 反射注册所有 ISettingDefinitionProvider,并注册 ISettingDefinitionManager 单例;
  • 注册各层 SettingValueProvider(Configuration 单例,User/Tenant/System 为 Scoped);
  • 启动时 SettingDefinitionManager.Initialize(...) 同步定义,ISettingManager.InitializeFromConfigurationAsync() 把 appsettings 种子化为系统设置。

消费方只需 AddFreeKitIdentity() 或对应模块启动类即可获得上述全部能力(具体接入方式见 Identity 模块索引)。

相关文档