1. 程式人生 > >SeaweedFS在.net core下的實踐方案(續一)

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();
        }

        public
IFileInfo 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)
                    }
                );
            }