SeaweedFS在.net core下的實踐方案(續一)
前言
我們之前已經完成了SeaweedFS在.net core下的使用了,但是說實話,還是不夠,於是,我的目光盯住了IApplicationBuilder的擴充套件方法UseStaticFiles
這個可是好東西啊,我們訪問資源的靜態檔案對映,你懂我的意思吧,對這裡下手~
前戲
開工之前,我們轉到定義看看
StaticFileOptions,這個就是我們自定義亂嗨的前提
它有兩個,我們DIY需要用到的引數
RequestPath、FileProvider
顧名思義,前者是訪問路徑的前置地址
RequestPath的值是wwwroot,那麼我們訪問
http://url/wwwroot/XX.字尾 才會觸發這個,而且,一定要是帶字尾的才觸發
後者是,前置地址觸發的基礎上才呼叫的
我們給他安排一下
實現
因為核心引數FileProvider型別為IFileProvider,所以,我們寫一個實現類吧
public class SeaweedFSFileProvider : IFileProvider { public IDirectoryContents GetDirectoryContents(string subpath) { throw new NotImplementedException(); } publicIFileInfo GetFileInfo(string subpath) { throw new NotImplementedException(); } public IChangeToken Watch(string filter) { throw new NotImplementedException(); } }
我們現在需要用到的是GetFileInfo這個方法,另外兩個,並不會觸發(嚴謹一點說,GetDirectoryContents,我們訪問無論是資源目錄路徑,還是完整的資源路徑,都不會觸發)
比如我們wwwroot這個資料夾是資源路徑
無論是
http://url/wwwroot/ 還是 http://url/wwwroot,都不觸發
這個也沒觸發~
emmmmm,可能研究太淺了,這兩個介面方法是給其他實現類提供的定製化功能?比如FileServer?
GetFileInfo方法的返回值是IFileInfo
這個介面,觸發檔案返回的順序是
Exists屬性->Length屬性->LastModified屬性->Exists屬性->PhysicalPath屬性->CreateReadStream方法
我們寫一個實現
public class SeaweedFSFileInfo : IFileInfo { public bool Exists { get; set; } public long Length => new MemoryStream(Context).Length; public string PhysicalPath { get; set; } public string Name { get; set; } public DateTimeOffset LastModified { get; } public bool IsDirectory => false; private byte[] Context { get; } public SeaweedFSFileInfo() { } public SeaweedFSFileInfo(string physicalPath, byte[] context) { Context = context; PhysicalPath = physicalPath; Name = Path.GetFileName(PhysicalPath); LastModified = DateTimeOffset.Now; Exists = true; } public Stream CreateReadStream() { return new MemoryStream(Context); } }
我們修改一下SeaweedFSFileProvider,這裡注入一個IFileService
因為我們希望整個SeaweedFSFileProvider他只依賴於IFileService,而不過多依賴SeaweedFS的實現,不會讓程式碼簡潔性受損
public class SeaweedFSFileProvider : IFileProvider { private IFileService Service { get; } public SeaweedFSFileProvider(IFileService service) { Service = service; } public IDirectoryContents GetDirectoryContents(string subpath) { throw new NotImplementedException(); } public IFileInfo GetFileInfo(string subpath) { throw new NotImplementedException(); } public IChangeToken Watch(string filter) { throw new NotImplementedException(); } }
我們轉到介面IFileService,寫一個介面
public interface IFileService { Task<SeaweedFSDirAssignModel> GetUploadFileUrlAsync(); Task<SeaweedFSUploadResponse> UploadFileAsync(string url,byte[] context); IFileInfo GetFileInfo(string subpath); }
再轉到實現類
增加這個實現
public IFileInfo GetFileInfo(string subpath) { throw new NotImplementedException(); }
我們去外層的SeaweedFSFileProvider修改一下
public class SeaweedFSFileProvider : IFileProvider { private IFileService Service { get; } public SeaweedFSFileProvider(IFileService service) { Service = service; } public IDirectoryContents GetDirectoryContents(string subpath) { throw new NotImplementedException(); } public IFileInfo GetFileInfo(string subpath) { return Service.GetFileInfo(subpath); } public IChangeToken Watch(string filter) { throw new NotImplementedException(); } }
這樣IFileService 裡面變成什麼樣,都跟這層沒關係了
我們定義一個IFileInfoFactory
public interface IFileInfoFactory { bool Contains(string filepath); IFileInfo GetFileInfo(string filepath); IFileInfo AddFileInfo(string filepath, byte[] context); IFileInfo AddNotExists(string filepath); }
再寫一個預設實現
public class FileInfoFactory: IFileInfoFactory { private List<IFileInfo> FileInfo { get; } = new List<IFileInfo>(); public bool Contains(string filepath) { return FileInfo.Any(file => file.PhysicalPath.Equals(filepath)); } public IFileInfo GetFileInfo(string filepath) { return FileInfo.FirstOrDefault(file => file.PhysicalPath.Equals(filepath)); } public IFileInfo AddFileInfo(string filepath,byte[] context) { var info = new SeaweedFSFileInfo(filepath, context); FileInfo.Add(info); return info; } public IFileInfo AddNotExists(string filepath) { var info = new SeaweedFSFileInfo(); FileInfo.Add(info); return info; } }
我們修改一下
SeaweedFSService的預設實現,增加一個注入IFileInfoFactory
private IFileInfoFactory FileInfoFactory { get; } public SeaweedFSService(IOptions<SeaweedFSServiceConfiguration> options, IFileInfoFactory fileInfoFactory) { Configuration = options.Value; FileInfoFactory = fileInfoFactory; }
我們實現一下
GetFileInfo方法
public IFileInfo GetFileInfo(string subpath) { using (var client = HttpClientFactory.Create()) { var path = subpath.Replace(Path.GetExtension(subpath), ""); var splits = path.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); if (splits.Length == 0) { return FileInfoFactory.AddNotExists(subpath); } else { var fid = $"{splits[0]},{splits[1]}"; var response = client.GetAsync($"http://{Configuration.BaseUrl}/{fid}") .GetAwaiter() .GetResult(); if (response.StatusCode == HttpStatusCode.NotFound) { return FileInfoFactory.AddNotExists(subpath); } else { var context = response.Content; var bytes = context.ReadAsByteArrayAsync() .GetAwaiter() .GetResult(); if (FileInfoFactory.Contains(subpath)) { return FileInfoFactory.GetFileInfo(subpath); } else { return FileInfoFactory.AddFileInfo(subpath, bytes); } } } } }
這個時候,我們測試一下
大功告成,撒花
優化
但是我們可能場景是這個檔案上傳了,就不再修改了,修改後檔案,變成新路徑,這樣,檔案就始終是靜態的,那麼這樣反覆http請求就沒意義了
所以,我們修改一下
private SeaweedFSServiceConfiguration Configuration { get; } private IFileInfoFactory FileInfoFactory { get; } private IDistributedCache Cache { get; } public SeaweedFSService(IOptions<SeaweedFSServiceConfiguration> options, IFileInfoFactory fileInfoFactory, IDistributedCache cache) { Configuration = options.Value; FileInfoFactory = fileInfoFactory; Cache = cache; }
增加了一個分散式快取
我們就找這個快取,能不能找到,還能找到,就說明已經快取了這個檔案資訊,就不再走http
修改一下GetFileInfo
public IFileInfo GetFileInfo(string subpath) { var key = $"Distributed_Files_{subpath}"; var contextBytes = Cache.Get(key); if (contextBytes != null && FileInfoFactory.Contains(subpath)) { return FileInfoFactory.GetFileInfo(subpath); } else { using (var client = HttpClientFactory.Create()) { var path = subpath.Replace(Path.GetExtension(subpath), ""); var splits = path.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); Cache.Set(key, new byte[] { }); if (splits.Length == 0) { return FileInfoFactory.AddNotExists(subpath); } else { var fid = $"{splits[0]},{splits[1]}"; var response = client.GetAsync($"http://{Configuration.BaseUrl}/{fid}") .GetAwaiter() .GetResult(); if (response.StatusCode == HttpStatusCode.NotFound) { return FileInfoFactory.AddNotExists(subpath); } else { var context = response.Content; var bytes = context.ReadAsByteArrayAsync() .GetAwaiter() .GetResult(); if (FileInfoFactory.Contains(subpath)) { return FileInfoFactory.GetFileInfo(subpath); } else { return FileInfoFactory.AddFileInfo(subpath, bytes); } } } } } }
這樣訪問的地址,快取沒失效之前,並且在檔案快取裡面,就不再走http請求了
附
我們附上入口的程式碼
ConfigureServices方法內增加
services.AddDistributedMemoryCache();
這樣就啟用了預設的分散式快取介面,後期要替換的實現,只用更換這裡的具體實現就好了,我們不依賴具體實現
Configure方法內增加程式碼
using (var services = app.ApplicationServices.CreateScope()) { var fileService = services.ServiceProvider.GetRequiredService<IFileService>(); app.UseStaticFiles( new StaticFileOptions { RequestPath = "/Resource", FileProvider = new SeaweedFSFileProvider(fileService) } ); }