一直對分布式的文件儲存系統很感興趣,最開始關注淘寶的TFS(Taobao File System),好像擱淺了,官方地址無法訪問,github上面,各種編譯問題,無意間發現了SeaweedFS
測試了一番,寫個應用的文章和.net core實踐的短文分享一下
SeaweedFS如何使用
SeaweedFS的Releases下面下載成品,1.20(主要原因是懶,不想去編譯)
運行命令
weed master
再掛載兩個分布的服務
weed volume -dir="D:/FileService/Volume/1" -max=1000 -mserver="localhost:9333" -port=8080
weed volume -dir="D:/FileService/Volume/2" -max=1000 -mserver="localhost:9333" -port=8081
我們在訪問一下
http://localhost:9333/dir/assign
返回可能是這樣的內容
{"fid":"1,1642d6a0d7","url":"127.0.0.1:8081","publicUrl":"127.0.0.1:8081","count":1}
我們解釋一下
fid是我們需要的上傳的參數
publicUrl是我們實際上需要上傳的地址
我們這次上傳的目標地址是
http://publicUrl/fid
http://127.0.0.1:8081/1,1642d6a0d7
上傳的參數file是對應的文件,上傳類型是form-data,就是標准的html表單提交方式
返回你的類型可能是
{ "name": "150106109346115258.jpg", "size": 206354, "eTag": "9e663632" }
這個etag,經常做web緩存的人,肯定不陌生,http緩存的策略
訪問地址則為
http://127.0.0.1:8081/1,1642d6a0d7
http://127.0.0.1:8081/1/1642d6a0d7
http://127.0.0.1:8081/1/1642d6a0d7/150106109346115258.jpg
SeaweedFS支持多數據中心,這個在官方github有提到,SeaweedFS自帶健康檢查,內部走的GRPC做健康檢查,所以請保持分布的服務端口,外界可訪問,無論是docker還是虛擬機、VPS,最終上傳還是走的那個端口
.Net Core下的實踐
我們先把兩個返回的實體對象做一下
public class SeaweedFSDirAssignModel { public string Fid { get; set; } public string Url { get; set; } public string PublicUrl { get; set; } public int Count { get; set; } }
public class SeaweedFSUploadResponse { public string Name { get; set; } public int Size { get; set; } public string ETag { get; set; } }
我們再根據這兩個實體,設計一個上傳服務
public interface IFileService { Task<SeaweedFSDirAssignModel> GetUploadFileUrlAsync(); Task<SeaweedFSUploadResponse> UploadFileAsync(string url,byte[] context); }
再設計一個注入的參數
public class SeaweedFSServiceConfiguration { public string BaseUrl { get; set; } = "localhost:9333"; public string DirAssign { get; set; } = "/dir/assign"; }
DirAssign這個是默認的參數,如果要用數據中心的話,這個就可以自定義修改了
public class SeaweedFSService : IFileService { private SeaweedFSServiceConfiguration Configuration { get; } public SeaweedFSService(IOptions<SeaweedFSServiceConfiguration> options) { Configuration = options.Value; } public async Task<SeaweedFSDirAssignModel> GetUploadFileUrlAsync() { using (var client = HttpClientFactory.Create()) { var url = $"http://{Configuration.BaseUrl}{Configuration.DirAssign}"; var response = await client.GetAsync(url); var body = await response.Content.ReadAsStringAsync(); var json = JsonConvert.DeserializeObject<SeaweedFSDirAssignModel>(body); return json; } } public async Task<SeaweedFSUploadResponse> UploadFileAsync(string url, byte[] context) { using (var client = HttpClientFactory.Create()) { using (var multipartContext = new MultipartFormDataContent()) { multipartContext.Add( new ByteArrayContent( context ), "file" ); var fileUrl = $"{url}"; var response = await client.PostAsync(fileUrl, multipartContext); var body = await response.Content.ReadAsStringAsync(); var json = JsonConvert.DeserializeObject<SeaweedFSUploadResponse>(body); return json; } } } }
在Startup.cs的注入一下
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); #region 注入服務 services.Configure<SeaweedFSServiceConfiguration>(options=> new SeaweedFSServiceConfiguration()); services.AddScoped<IFileService, SeaweedFSService>(); #endregion }
測試文件上傳
先寫一個擴展方法,我們希望看見的返回地址是
http://127.0.0.1:8081/1,1642d6a0d7
http://127.0.0.1:8081/1/1642d6a0d7
這個地址的后者
實現如下
internal static class SeaweedFSDirAssignModelExtension { public static string ToFileName(this SeaweedFSDirAssignModel response) { var splits = response.Fid.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); if (splits.Length != 2) throw new ArgumentException($"String Split Fail, value:{response.Fid}"); return $"{splits[0]}/{splits[1]}"; } }
寫一個控制器測試上傳
構建一下返回參數和入參
public class UploadFileResponseModel
{
public string FileName { get; set; }
}
public class UploadFileRequestModel
{
public IFormFile File { get; set; }
}
控制器代碼如下
[Route("api/[controller]")] [ApiController] public class FileController : ControllerBase { private IFileService FileService { get; } public FileController(IFileService fileService) { FileService = fileService; } [HttpPost("Upload")] public async Task<UploadFileResponseModel> Upload([FromForm]UploadFileRequestModel param) { #region IO讀取 var stream = param.File.OpenReadStream(); var bytes = new byte[param.File.Length]; await stream.ReadAsync(bytes, 0, bytes.Length); #endregion var fileInfo = await FileService.GetUploadFileUrlAsync(); var url = $"http://{fileInfo.PublicUrl}/{fileInfo.Fid}"; var uploadResponse = await FileService.UploadFileAsync(url, bytes); return new UploadFileResponseModel() { FileName = $"{fileInfo.ToFileName()}" }; } }
我們用postman測試一下
ok,上傳成功,我們訪問
http://localhost:9333/4,1ca657cf3f
http://localhost:9333/4/1ca657cf3f
http://127.0.0.1:8080/4,1ca657cf3f
http://127.0.0.1:8080/4/1ca657cf3f
前面兩個地址會轉跳到后面兩個地址
后記
我這代碼測試,會出現,不返回name字段的情況
{
"name": "150106109346115258.jpg",
"size": 206354,
"eTag": "9e663632"
}
這種json格式是直接上傳的返回
但是我們這個上傳服務會變成
{
"size": 206354,
"eTag": "9e663632"
}
我見了鬼了,誰有發現原因,請告訴我一下,拜托了