文件与通用附件
文件能力位于 FreeKit.Identity.Infrastructure,由两套相关但不同的模型组成:
KitFile记录一次文件上传及其存储位置,主键是long。CommonAttachment记录某个业务实体使用的附件快照,主键是Guid。
CommonAttachment 当前没有 KitFileId 外键。上传后创建附件时,需要把 FileDto 的名称、路径和大小复制到附件输入;不要把它描述成一张只保存文件 Id 的关联表。
KitFile
实体路径:src/Services/Identity/FreeKit.Identity.Infrastructure/Domain/Files/KitFile.cs。
public class KitFile : FullAuditEntity<long, long>, ITenant
| 字段 | 类型 | 含义 |
|---|---|---|
Id | long | 文件记录主键 |
TenantId | Guid? | 租户 |
FileMetaId | long? | 可选 KitFileMeta 主键 |
Extension | string | 扩展名 |
Hash | string | 文件哈希,用于去重 |
Name | string | 文件名称 |
Host | string? | 存储域名 |
Path | string | 存储相对路径/对象 Key |
Size | long? | 文件字节数 |
Type | FileUploadType? | Local、Qiniu、CloudFlare 等存储类型 |
IsFileExist | bool | 后端文件是否仍存在 |
KitFileMeta 也是 long 主键,只包含 Hash 和 Name;它不是任意 Key/Value 元数据表。
API 返回的 FileDto 包含:
public class FileDto : EntityDto<long>
{
public string Key { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public string Url { get; set; }
public long Size { get; set; }
public FileUploadType Type { get; set; }
}
CommonAttachment
实体路径:src/Services/Identity/FreeKit.Identity.Infrastructure/Domain/CommonAttachments/CommonAttachment.cs。
public class CommonAttachment : FullAuditEntity, ITenant
| 字段 | 类型 | 含义 |
|---|---|---|
Id | Guid | 附件记录主键 |
EntityId | Guid | 业务实体主键 |
EntityType | string | 已注册的业务实体类型,例如 Article |
Category | string? | 业务分类,例如 Article/Content |
FileName | string | 文件名快照 |
FilePath | string | 文件路径快照 |
FileSize | long | 文件大小快照 |
AttachmentType | AttachmentType | Image、Video、Audio、Document、Other |
FileMetaId | int? | 历史元数据标识;当前没有到 KitFileMeta(long) 的导航或外键 |
Sort | int | 升序展示顺序 |
AltText | string | 图片描述/Alt 文本 |
IsFileExist | bool | 文件存在状态 |
列表 DTO 会根据 FilePath 通过 IFileManager.GetFileUrlAsync 补充 FileUrl。
文件 API
Controller:FreeKit.Identity.Infrastructure.Application.Files.FileController。
基础路由:/api/identity/file,继承 FreeKitController,默认要求登录。
| 方法 | 路由 | 额外权限 | 用途 |
|---|---|---|---|
| POST | /uploads | 登录 | files 表单字段,多文件上传 |
| POST | /upload | 登录 | file 表单字段,单文件上传 |
| POST | /upload-from-url?url=... | 登录 | 从 URL 拉取并保存 |
| GET | /{id:long} | 登录 | 获取文件信息和可访问 URL |
| GET | / | Identity.Files.GetList | 分页查询文件 |
| PUT | /{id:long} | Identity.Files.GetList | 修改文件名称 |
| DELETE | /{id} | Identity.Files.GetList | 通过审计仓储软删除文件记录 |
| PUT | /check_all_file_exists | Identity.Files.GetList | 批量刷新本地文件存在状态 |
| GET | /check_file_exists/{id:long} | Identity.Files.GetList | 检查单个文件 |
三个头像辅助接口 create_avatar、create_avatar_url_by_name、get_random_avatar 当前标记为匿名。
请求经过 MultipartRequestHelper 检查 Content-Type。多文件上传还会检查 NumLimit、总大小和扩展名;单文件同样执行扩展名白名单/黑名单检查。
通用附件 API
Controller:FreeKit.Identity.Infrastructure.Application.CommonAttachments.CommonAttachmentController。
基础路由:/api/identity/common-attachment。
| 方法 | 路由 | 认证 | 用途 |
|---|---|---|---|
| POST | / | 登录 | 创建一个附件快照 |
| DELETE | /{id:guid} | 登录 | 删除附件;非 Admin 只能删除自己的记录 |
| GET | /?entityId=...&entityType=... | 匿名 | 分页查询附件 |
| GET | /{entityId:guid} | 登录 | 查询某实体的全部附件 |
| PUT | /check_all_file_exists | 登录 | 刷新本地附件存在状态 |
匿名列表至少需要 EntityId、EntityIds 或 EntityType 之一,否则 Manager 返回空结果。查询还支持 Category 和 AttachmentType。
批量新增/更新、按实体删除和云端同步存在于 ICommonAttachmentService,但当前没有由这个 Controller 直接公开成 REST Action;它们由文章、沸点、举报等业务服务内部调用。
上传后创建附件
下面代码使用真实的 FileDto 和 AttachmentCreateInput 字段:
FileDto file = await fileService.UploadAsync(formFile);
await attachmentService.AddAttachmentAsync(new AttachmentCreateInput
{
EntityId = articleId,
EntityType = "Article",
Category = "Article/Content",
AttachmentType = AttachmentType.Image,
FileName = file.Name,
FilePath = file.Path,
FileSize = file.Size
});
CmsKit 在 CmsKitModuleStartup.Subdomains.cs 注册了当前允许的组合:
Article/Cover:ImageArticle/Content:Image、Video、Audio、Document、OtherShortMsg/Media:Image、Video、AudioUserReport/Evidence:Image
CommonAttachmentValidatorBase 会先确认 EntityType 已注册,再检查分类和附件类型;配置了 IEntityAccessValidator 时还会验证当前用户能否访问目标实体。
如果 AttachmentType 使用默认值,CommonAttachmentService 当前会按扩展名推断类型用于规则校验,但不会把推断值回写到传给 Manager 的输入。因此调用方应显式提交 AttachmentType;否则记录可能保存枚举默认值 0。这是当前实现需要后续修正的边界,文档示例不依赖隐式推断。
存储配置
IdentityInfrastructureModuleStartup 绑定 FileStorage,并注册当前确实存在的三个 keyed uploader:
LocalFileManagerQiniuManagerCloudFlareR2
安全的本地开发示例:
{
"FileStorage": {
"MaxFileSize": 1073741824,
"NumLimit": 3,
"Include": "",
"Exclude": ".exe,.dll,.jar",
"ServiceName": "LocalFileManager",
"ImgExtensions": "jpg,jpeg,png,gif,webp",
"LocalFile": {
"RootPath": "",
"PrefixPath": "assets",
"Host": "https://api.example.com/kit_api/"
}
}
}
ServiceName 必须与注册 key 完全一致。Cloudflare R2 和七牛配置中的访问凭据必须通过环境变量、Secret Manager 或用户机密提供,不要写进文档或提交到仓库。
IdentityInfrastructureModuleStartup 将 multipart body 上限设为 1 GiB、单个普通表单值上限设为 8 MiB;文件服务自己的 MaxFileSize 和 NumLimit 仍需按业务场景配置,不能只依赖 Kestrel/Form 上限。
结构同步
IdentityInfrastructureModuleStartup.Configure 在 DEBUG 下同步:
KitFileMetaKitFileCommonAttachment
Release 启动不会依靠这段 SyncStructure 建表,部署文档应要求显式迁移或提前初始化数据库。
源码索引
src/Services/Identity/FreeKit.Identity.Infrastructure/Application/Files/FileController.cssrc/Services/Identity/FreeKit.Identity.Infrastructure/Application/Files/FileService.cssrc/Services/Identity/FreeKit.Identity.Infrastructure/Application/CommonAttachments/CommonAttachmentController.cssrc/Services/Identity/FreeKit.Identity.Infrastructure/Application/CommonAttachments/CommonAttachmentService.cssrc/Services/Identity/FreeKit.Identity.Infrastructure/Domain/CommonAttachments/CommonAttachmentManager.cssrc/Services/Identity/FreeKit.Identity.Infrastructure/IdentityInfrastructureModuleStartup.cs