如何在tusdotnet中针对每次上传请求动态覆盖存储路径?
解决tusdotnet动态设置单请求存储路径的问题
tusdotnet默认在启动时全局配置存储实例,直接在OnBeforeCreateAsync等事件中替换ctx.Store不会生效——因为框架内部会复用全局初始化的存储实例,事件上下文的Store只是引用,无法覆盖全局配置。要实现按请求动态设置存储路径,需要通过自定义ITusStore实现来适配。
解决方案:自定义动态存储类
创建一个包装类,通过IHttpContextAccessor获取当前请求上下文,解析Query参数生成对应路径,动态创建TusDiskStore处理当前请求的存储操作。
1. 实现自定义动态存储类
public class DynamicTusDiskStore : ITusStore, ITusCreationStore, ITusReadableStore, ITusDeletableStore { private readonly IHttpContextAccessor _httpContextAccessor; // 可选:缓存已创建的TusDiskStore实例,减少重复创建开销 private readonly ConcurrentDictionary<string, TusDiskStore> _storeCache = new(); public DynamicTusDiskStore(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } private TusDiskStore GetCurrentStore() { var httpContext = _httpContextAccessor.HttpContext ?? throw new InvalidOperationException("HttpContext不可用"); // 从Query参数解析目标路径(替换为你的GetUploadStoreDir实现) var uploadDir = GetUploadStoreDir(httpContext.Request.Query); var fullPath = Path.GetFullPath(uploadDir); // 创建目录(确保路径合法,避免遍历攻击) if (!Directory.Exists(fullPath)) Directory.CreateDirectory(fullPath); // 从缓存获取或创建TusDiskStore实例 return _storeCache.GetOrAdd(fullPath, path => new TusDiskStore(path)); } // 所有存储接口方法委托给动态生成的TusDiskStore实例 public Task<ITusFile> GetFileAsync(string fileId, CancellationToken cancellationToken) => GetCurrentStore().GetFileAsync(fileId, cancellationToken); public Task DeleteFileAsync(string fileId, CancellationToken cancellationToken) => GetCurrentStore().DeleteFileAsync(fileId, cancellationToken); public Task<long> AppendDataAsync(string fileId, Stream stream, CancellationToken cancellationToken) => GetCurrentStore().AppendDataAsync(fileId, stream, cancellationToken); public Task<string> CreateFileAsync(long uploadLength, string metadata, CancellationToken cancellationToken) => ((ITusCreationStore)GetCurrentStore()).CreateFileAsync(uploadLength, metadata, cancellationToken); public Task<long> GetUploadLengthAsync(string fileId, CancellationToken cancellationToken) => GetCurrentStore().GetUploadLengthAsync(fileId, cancellationToken); public Task<long> GetUploadOffsetAsync(string fileId, CancellationToken cancellationToken) => GetCurrentStore().GetUploadOffsetAsync(fileId, cancellationToken); public Task SetUploadLengthAsync(string fileId, long uploadLength, CancellationToken cancellationToken) => GetCurrentStore().SetUploadLengthAsync(fileId, uploadLength, cancellationToken); // 若需支持过期、校验等其他接口,同理添加委托实现 }
2. 注册服务并配置tusdotnet
在Startup.cs中注册IHttpContextAccessor,并使用自定义存储类:
public void ConfigureServices(IServiceCollection services) { // 注册HttpContext访问器 services.AddHttpContextAccessor(); // 其他服务注册... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 其他中间件配置... app.UseTus(context => new DefaultTusConfiguration { UrlPath = "/files", // 使用自定义动态存储类 Store = new DynamicTusDiskStore(context.RequestServices.GetRequiredService<IHttpContextAccessor>()), Events = new Events { OnFileCompleteAsync = async eventContext => { // 从当前请求上下文重新解析路径,获取对应存储实例 var httpContext = eventContext.HttpContext; var uploadDir = GetUploadStoreDir(httpContext.Request.Query); var fullPath = Path.GetFullPath(uploadDir); var store = new TusDiskStore(fullPath); ITusFile file = await store.GetFileAsync(eventContext.FileId, CancellationToken.None); var result = await BuildFilePlacementAsync(file, store); if (!result.Success) { throw new MyProcessingException("文件处理失败"); } } } }); }
关键注意事项
- 路径安全:必须对Query参数中的路径进行合法性校验,防止路径遍历攻击(比如限制路径只能在指定根目录下)。
- 缓存策略:使用
ConcurrentDictionary缓存TusDiskStore实例时,需考虑定期清理长期未使用的实例,避免内存泄漏。 - 接口覆盖:根据你的业务需求,实现
ITusStore的所有必要接口(如ITusExpirationStore用于过期文件清理)。
内容的提问来源于stack exchange,提问作者Zap
相关产品推荐
相关产品推荐

