- CMS
- 后端
- Web框架
【免费下载链接】OrchardCore
Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
导读
本文围绕 Orchard Core 的ITempDirectoryProvider临时文件存储抽象展开,介绍它在分片上传、TUS 断点续传、部署/Recipe 导入导出等场景中的核心作用,说明如何通过OrchardCore:TempDirectory:Path一项全局配置把临时文件从系统临时目录迁移到更大的共享卷(如 Azure Files、AWS EFS/FSx),并给出面向开发者的 API 使用方式与实现替换方法。读完本文,你将掌握 Orchard Core 临时文件目录的租户隔离机制、配置落地方式,以及如何在自己的功能模块中正确消费这套抽象。
一、为什么需要ITempDirectoryProvider
Orchard Core 在运行中需要写临时文件的场景非常多,例如:
- 进行中的分片上传(chunked upload)——大文件被切分为多个分片,跨多次请求写入同一个临时文件;
- 可断点续传的 TUS 上传——部分上传数据在完成前需要持久保存;
- 部署 / Recipe 导入导出——先落地压缩包,再解压到临时目录处理;
- 站点 Setup 前写入磁盘的 Recipe 文件。
这些消费者早期直接使用Path.GetTempPath()写系统临时目录。但系统临时目录的空间常常受限:小型系统临时卷、容器化的临时磁盘都容易在小空间下被大量并发大文件上传填满。因此 Orchard Core 引入了ITempDirectoryProvider抽象(位于OrchardCore.FileStorage命名空间),让临时文件的基路径可配置,操作者可以把它指向一个按工作负载规模规划好的卷——包括多实例部署下的共享网络卷,这样在一台实例上开始的断点上传可以由另一台实例继续完成。
源码佐证:抽象只给路径,不碰文件内容
从 ITempDirectoryProvider.cs 可以看到,这个抽象只负责分发真实本地文件系统路径,调用方自己用System.IOAPI 完成实际读写,接口本身不读不写文件内容:
GetRootDirectory():返回租户作用域的根目录(按需创建);CreateTempSubdirectory(string prefix = null):在根目录下创建唯一命名的新子目录,可选前缀;GetTempFileName(string extension = null):返回根目录下一个唯一的新文件路径(文件本身不会创建),扩展名可带或不带前导点。
二、默认实现与租户隔离
默认实现DefaultTempDirectoryProvider是基于文件系统的。其构造函数逻辑(见 DefaultTempDirectoryProvider.cs)非常清晰:
var basePath = options.Value.Path; if (string.IsNullOrWhiteSpace(basePath)) { basePath = Path.GetTempPath(); } // Scope temporary files per tenant so that tenants can neither collide with nor observe each other's files. _rootPath = Path.Combine(basePath, shellSettings.Name);也就是说:
- 读取
TempDirectoryOptions.Path,未配置时回退到Path.GetTempPath(); - 用
Path.Combine(basePath, shellSettings.Name)拼出租户隔离的根路径。
目录布局
临时文件一律按{Path}/{TenantName}/...布局。租户子目录由实现自动拼接,操作者永远不需要自己拼。租户隔离的意义在于:不同租户的临时文件互不冲突、互不可见——代码注释明确写道 "tenants can neither collide with nor observe each other's files"。
权限与回退行为
- 在 Unix-like 系统上,新建的租户目录使用仅属主权限(
UnixFileMode.UserRead | UserWrite | UserExecute,即700),与Directory.CreateTempSubdirectory()的行为一致(见 DefaultTempDirectoryProvider.cs); - 当
Path未设置时,回退到操作系统临时目录,保持旧行为不变。
这些行为都有测试覆盖。在 DefaultTempDirectoryProviderTests.cs 中可以看到:
GetRootDirectory_ReturnsTenantScopedPath_AndCreatesIt:验证根路径为{basePath}/{TenantName}且目录被创建;GetRootDirectory_OnUnix_CreatesWithOwnerOnlyPermissions:验证 Unix 下的700权限;DifferentTenants_GetIsolatedRoots:验证不同租户得到隔离的根路径;EmptyTempPath_FallsBackToSystemTempPath:验证未配置时回退到Path.GetTempPath();GetTempFileName_HonorsExtension:验证扩展名无论带不带前导点,最终都规范为.zip这样的带点形式;CreateTempSubdirectory_AppliesPrefixToDirectoryName:验证前缀被拼到生成的目录名前。
三、全局配置:OrchardCore:TempDirectory
基路径由OrchardCore:TempDirectory配置节全局控制,对应 TempDirectoryOptions.cs 中的Path属性。完整配置示例:
{ "OrchardCore": { "TempDirectory": { // Base path under which tenant-scoped temporary files are stored. // When omitted, the operating system temporary directory (Path.GetTempPath()) is used. "Path": "/mnt/shared/temp" } } }配置如何生效
从注册代码(见 ServiceCollectionExtensions.cs)可以看到两个关键细节:
services.TryAddSingleton<ITempDirectoryProvider, DefaultTempDirectoryProvider>()——它是租户级单例(不是宿主级单例),因为它依赖租户的ShellSettings;services.Configure<TempDirectoryOptions>(configuration.GetSection("TempDirectory"))——配置节从租户级IShellConfiguration读取TempDirectory段,绑定到选项类。
因此这一项设置会作用于所有临时文件消费者:媒体分片上传、TUS 上传、部署与 Recipe 的导入/导出、租户 Recipe 上传等,全部跟随OrchardCore:TempDirectory:Path。
TUS 断点续传的例外说明
需要注意一个细节:可断点续传的TUS 部分上传数据存放在该位置的TusUploads子目录下。源码佐证见 DiskTusTempStore.cs——它用Path.Combine(tempDirectoryProvider.GetRootDirectory(), "TusUploads")拼出存储目录,因此同样遵循OrchardCore:TempDirectory:Path。
四、挂载共享文件系统(Azure Files、AWS EFS/FSx)
因为DefaultTempDirectoryProvider只使用普通的System.IO操作,任何以已挂载文件系统路径形式呈现的存储都无需改代码即可工作——本地磁盘、SAN/NAS 卷、SMB 共享(Azure Files)、NFS 共享(AWS EFS/FSx)均可。在操作系统层面完成挂载后,把Path设为挂载点即可。
Linux / 容器(SMB 挂载 Azure Files)
sudo mkdir -p /mnt/octemp sudo mount -t cifs //<account>.file.core.windows.net/<share> /mnt/octemp \ -o vers=3.0,username=<account>,password=<storage-key>,dir_mode=0777,file_mode=0777,serverino,nosharesock,actimeo=30{ "OrchardCore": { "TempDirectory": { "Path": "/mnt/octemp" } } }Windows
New-SmbGlobalMapping -RemotePath "\\<account>.file.core.windows.net\<share>" -Credential $cred -Persistent $true -LocalPath Z:{ "OrchardCore": { "TempDirectory": { "Path": "Z:\\octemp" } } }Azure App Service / Container Apps
使用平台自带的 Azure Files 挂载能力(App Service:Configuration → Path mappings → Azure Storage Mounts;Container Apps:AzureFile卷),然后把Path设为平台挂载路径(如/mounts/octemp)。凭据与重连都由平台托管。
重要警告:必须是文件共享,不能是对象存储
把Path指向挂载的文件共享,而不是对象存储。临时文件消费者要求真实、可随机访问的本地文件:
- 分片上传路径会执行
Seek/SetLength,并跨请求重新打开同一个文件(参见 ChunkFileUploadService.cs 中对FileStream.Seek(contentRange.From, SeekOrigin.Begin)的使用); - 部署导入会使用
ZipFile.ExtractToDirectory和PhysicalFileProvider。
Azure Files(SMB)和 AWS EFS/FSx 支持这种语义;Azure Blob 和 AWS S3 对象存储不支持。唯一的例外是 TUS 断点续传:它可以通过OrchardCore.Media.Azure与OrchardCore.Media.AmazonS3功能中的ITusTempStore实现直接以对象存储为目标(仓库中可找到 AzureBlobTusTempStore.cs 等实现)。
运维注意事项
- 确保挂载点对应用运行身份可写;
- 共享卷与实例保持在同一区域,以降低延迟;
- 现有清理任务(如媒体模块的
TemporaryFileLifetime过期清理)现在会作用于配置后的路径——例如 ChunkFileUploadService.cs 中的PurgeTempDirectory()会按TemporaryFileLifetime清理超龄临时文件。
五、在功能模块中消费ITempDirectoryProvider
如果你的功能需要写临时文件,请注入ITempDirectoryProvider,而不是直接调用Path.GetTempPath()。这样你的临时文件会自动落入配置好的、租户隔离的位置。
API 速查表
| 成员 | 用途 |
|---|---|
GetRootDirectory() | 租户作用域根目录(按需创建)。作为需要目录的工具的基础,例如TemporaryFileBuilder。 |
GetTempFileName(extension) | 唯一的文件路径(不会创建文件),用于写单个临时文件。扩展名可选,带或不带前导点均可。 |
CreateTempSubdirectory(prefix) | 新建的唯一子目录——用于解压归档,或通过PhysicalFileProvider暴露。可选前缀会拼到生成名称前。 |
完整示例:导入服务
using OrchardCore.FileStorage; public sealed class MyImportService { private readonly ITempDirectoryProvider _tempDirectoryProvider; public MyImportService(ITempDirectoryProvider tempDirectoryProvider) => _tempDirectoryProvider = tempDirectoryProvider; public async Task ImportAsync(IFormFile package, CancellationToken cancellationToken) { // A unique file path under {Path}/{TenantName}/ to save the upload. var archivePath = _tempDirectoryProvider.GetTempFileName(Path.GetExtension(package.FileName)); // A dedicated directory to extract into. var extractPath = _tempDirectoryProvider.CreateTempSubdirectory(); try { await using (var stream = File.Create(archivePath)) { await package.CopyToAsync(stream, cancellationToken); } ZipFile.ExtractToDirectory(archivePath, extractPath); // ... process the extracted files ... } finally { File.Delete(archivePath); Directory.Delete(extractPath, recursive: true); } } }记住两点
- 只管路径,清理自理:store 只负责给出路径,文件的创建、写入和清理完全由调用方负责。用完即删,store 不会替你跟踪或过期清理。
- 临时 ≠ 安全:
ITempDirectoryProvider只提供临时存储,它不扫描上传内容。当临时文件来自用户上传时,在永久落盘之前仍应经过FileCreationService的安全检查(详见上传安全文档 file-upload-security.md)。
六、替换默认实现
DefaultTempDirectoryProvider以TryAddSingleton方式按租户注册(见 ServiceCollectionExtensions.cs),因此模块可以用自己的实现替换:
services.Replace(ServiceDescriptor.Singleton<ITempDirectoryProvider, MyTempDirectoryProvider>());任何替代实现都必须遵守同一契约:返回真实本地文件系统路径,支持随机访问与目录枚举。这正是仓库中没有 Azure Blob 或 AWS S3 版ITempDirectoryProvider的原因——对象存储无法满足消费者依赖的“基于路径、可随机访问”的契约。要把临时存储搬到云上,正确做法是挂载文件共享(Azure Files、AWS EFS/FSx)并配置Path,即本文第四节所述方案。
七、小结与检查清单
ITempDirectoryProvider是 Orchard Core 统一管理临时文件的入口:一份OrchardCore:TempDirectory:Path配置,即可把分片上传、TUS 上传、部署/Recipe 导入导出等全部临时文件消费者迁移到大容量或共享卷,同时天然获得按租户的目录隔离。落地时请对照以下清单:
- 在生产环境显式配置
OrchardCore:TempDirectory:Path,避免依赖空间受限的系统临时目录; - 多实例部署时优先使用共享文件系统(Azure Files / AWS EFS/FSx),实现跨实例断点续传;
- 确认挂载点对应用运行身份可写、与实例同区域;
- 确认消费者要求的
Seek/SetLength、ZipFile.ExtractToDirectory、PhysicalFileProvider语义得到满足(文件共享可以,对象存储不行); - 自研功能一律注入
ITempDirectoryProvider,不要直接调Path.GetTempPath(); - 用完删除临时文件,上传文件在落盘前仍要走
FileCreationService安全检查。
参考文件速览
- 本文原始依据:temporary-file-storage.md
- 抽象接口:ITempDirectoryProvider.cs
- 默认实现:DefaultTempDirectoryProvider.cs
- 配置选项:TempDirectoryOptions.cs
- 注册与绑定:ServiceCollectionExtensions.cs
- 分片上传消费者:ChunkFileUploadService.cs
- TUS 磁盘存储:DiskTusTempStore.cs
- 单元测试:DefaultTempDirectoryProviderTests.cs
- 上传安全检查:file-upload-security.md
- CMS
- 后端
- Web框架
【免费下载链接】OrchardCore
Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
相关推荐
ArchiveBox 存储架构与配置指南:本地与远程文件系统选型、Rclone 挂载及 Docker 卷插件实战
ArchiveBox 存储架构与配置指南:本地与远程文件系统选型、Rclone 挂载及 Docker 卷插件实战 ArchiveBox 是一个开源的自托管网页存
后端数据工程第50章:一剑斩金丹
第50章:一剑斩金丹 开头: 金丹长老嘲讽林天不自量力 发展: 林天拔剑,展示剑意 高潮: 一剑斩杀金丹长老,全场震惊 结尾: 宗主现身:"有趣,我要收你为亲传
人工智能AI 应用AI 写作RAGAI 插件AI 技能三分钟打造专业音乐播放器:foobar2000终极美化指南
三分钟打造专业音乐播放器:foobar2000终极美化指南 还在为foobar2000单调的界面而烦恼吗? foobox美化方案 为你提供了一站式解决方案,让这
桌面应用音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考