此说明文档基于class="superseo">.netcore的实现方案编写,
阅读本文档之前请先熟悉netcore如何使用grpc。
syntax = "proto3";
option csharp_namespace = "FileService";
package file.grpc;
import "google/protobuf/wrappers.proto";
//文件传输体
message FilePart{
//文件名,不含路径,包含扩展名
string file_name = 1;
//文件内容,查询文件列表时内容为空
google.protobuf.BytesValue file_data = 2;
//文件存放命名空间
google.protobuf.StringValue name_space = 3;
}
//文件 *** 作结果
message FileOpResult{
//结果
bool ok = 1;
//传输成功会生成唯一标识
int64 file_id = 2;
google.protobuf.StringValue msg = 3;
}
//文件查询请求
message FileRequest {
//文件id
int64 file_id = 1;
}
//文件列表查询请求
message FileListRequest{
//文件名关键字
google.protobuf.StringValue keyword = 1;
//命名空间
google.protobuf.StringValue name_space = 2;
int32 page_index = 3;
int32 page_size = 4;
}
//文件列表
message FileListResponse{
repeated FilePart items = 1;
int32 page_index = 2;
int32 page_size = 3;
int32 total = 4;
}
//文件传输服务
service FileOP{
//向服务端发送文件
rpc SendFile(stream FilePart) returns (FileOpResult);
//从服务端获取文件
rpc GetFile(FileRequest) returns (stream FilePart);
//删除文件
rpc RemoveFile(FileRequest) returns (FileOpResult);
//查询文件列表
rpc GetFilePaged(FileListRequest) returns (FileListResponse);
}
grpc服务实现
public class FileService : FileOP.FileOPBase
{
//数据库交互,自行按需实现
ICommonService<FileIndex> _commonService;
public FileService(ICommonService<FileIndex> _commonService)
{
this._commonService = _commonService;
}
public override async Task<FileOpResult> SendFile(IAsyncStreamReader<FilePart> requestStream, ServerCallContext context)
{
//生成主键的算法自行实现
var file_id = Snowflake.Instance().GetId();
var idx = 0;
var filepath = string.Empty;
var nameSpace = string.Empty;
var filename = string.Empty;
FileStream fs = null;
try
{
//从请求流分片获取文件内容
while (await requestStream.MoveNext())
{
if (idx == 0)
{
filename = requestStream.Current.FileName;
var fileext = Path.GetExtension(filename);
if (!string.IsNullOrWhiteSpace(requestStream.Current.NameSpace))
{
nameSpace = requestStream.Current.NameSpace;
}
var savedir = $"savefile/{nameSpace}/{DateTime.Now:yyyy-MM-dd}";
filepath = Path.Combine(savedir, $"{file_id}{fileext}");
var savedir_full = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, savedir);
if (!Directory.Exists(savedir_full))
{
Directory.CreateDirectory(savedir_full);
}
fs = File.OpenWrite(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filepath));
}
//关键步骤,把文件内容写入文件流
requestStream.Current.FileData.WriteTo(fs);
idx++;
}
}
finally
{
fs?.Close();
fs?.Dispose();
}
var fileindex = new FileIndex
{
Id = file_id,
Name = filename,
NameSpace = nameSpace,
FilePath = filepath,
CreateTime = DateTime.Now,
LastUpdateTime = DateTime.Now
};
return new FileOpResult
{
//文件索引存到数据库,此处自行实现
Ok = await _commonService.Add(fileindex),
FileId = file_id
};
}
public override async Task GetFile(FileRequest request, IServerStreamWriter<FilePart> responseStream, ServerCallContext context)
{
//从数据库获取文件信息
var fileindex = await _commonService.Get(d => d.Id == request.FileId);
if (fileindex == null)
{
throw new Exception("文件不存在");
}
//最后访问时间
fileindex.LastUpdateTime = DateTime.Now;
_commonService.Update(fileindex, "LastUpdateTime");
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileindex.FilePath);
using (var fs = File.OpenRead(filepath))
{
var buffer = new byte[1024 * 1024];
var totallength = fs.Length;
//从文件流取分片发送到响应流
while (totallength > 0)
{
var len = await fs.ReadAsync(buffer);
totallength -= len;
await responseStream.WriteAsync(new FilePart
{
FileData = ByteString.CopyFrom(buffer, 0, len),
FileName = fileindex.Name,
NameSpace = fileindex.NameSpace
});
}
}
}
public override async Task<FileOpResult> RemoveFile(FileRequest request, ServerCallContext context)
{
var result = new FileOpResult
{
Ok = false,
FileId = 0
};
var fileindex = await _commonService.Get(d => d.Id == request.FileId);
if (fileindex == null)
{
result.Msg = "文件不存在";
return result;
}
result.Ok = await _commonService.Delete(fileindex);
if (result.Ok)
{
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileindex.FilePath);
File.Delete(filepath);
}
return result;
}
public override async Task<FileListResponse> GetFilePaged(FileListRequest request, ServerCallContext context)
{
string keyword = null;
if (!string.IsNullOrWhiteSpace(request.Keyword))
{
keyword = request.Keyword.ToLower();
}
string name_space = null;
if (!string.IsNullOrWhiteSpace(request.NameSpace))
{
name_space = request.NameSpace;
}
var query = await _commonService.GetPaged(d => (keyword == null || keyword == d.Name || d.Name.ToLower().Contains(keyword)) && (name_space == null || d.NameSpace == name_space), request.PageIndex, request.PageSize, d => d.CreateTime, Enums.OrderMode.Desc);
var result = new FileListResponse
{
PageIndex = query.PageIndex,
PageSize = query.PageSize,
Total = query.Total
};
result.Items.AddRange(query.Items.Select(d => new FilePart
{
FileName = d.Name,
NameSpace = d.NameSpace
}));
return result;
}
}
数据库的表结构参考,这里用的postgresql
-- Table: public."FileIndex"
-- DROP TABLE public."FileIndex";
CREATE TABLE public."FileIndex"
(
"Id" bigint NOT NULL,
"CreateTime" timestamp without time zone NOT NULL DEFAULT now(),
"LastUpdateTime" timestamp without time zone NOT NULL DEFAULT now(),
"LastUpdateUserId" character varying(50) COLLATE pg_catalog."default",
"CreateUserId" character varying(50) COLLATE pg_catalog."default",
"Description" character varying(50) COLLATE pg_catalog."default",
"IsDelete" boolean NOT NULL DEFAULT false,
"Name" character varying(100) COLLATE pg_catalog."default",
"FilePath" character varying(200) COLLATE pg_catalog."default",
"NameSpace" character varying(50) COLLATE pg_catalog."default"
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
grpc调用端
这里的案例是webapi调用grpc
[Route("[controller]")]
[ApiController]
public class FileController : ControllerBase
{
FileService.FileOP.FileOPClient _fileOPClient;
string appcode;
public FileController(FileService.FileOP.FileOPClient _fileOPClient)
{
this._fileOPClient = _fileOPClient;
appcode = "webapidemo";
}
///
/// 上传文件
///
///
[HttpPost, Route("upload")]
public async Task<string> Upload([FromForm] IFormCollection formData)
{
var filelist = formData.Files;
if (filelist.Count == 0)
{
var msg = new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType);
msg.Content = new StringContent("no file");
throw new Exception(msg.Content.ToString());
}
var postfile = filelist[0];
var stream = postfile.OpenReadStream();
var totallength = postfile.Length;
var clientCall = _fileOPClient.SendFile();
var RequestStream = clientCall.RequestStream;
var buffer = new byte[1024 * 1024];
FileOpResult fileOpResult;
try
{
//将待上传的文件流分片发给grpc服务端
while (totallength > 0)
{
var len = await stream.ReadAsync(buffer);
totallength -= len;
await RequestStream.WriteAsync(new FileService.FilePart
{
FileName = postfile.FileName,
NameSpace = appcode,
FileData = ByteString.CopyFrom(buffer, 0, len)
});
}
await RequestStream.CompleteAsync();
fileOpResult = await clientCall.ResponseAsync;
}
finally
{
stream.Close();
stream.Dispose();
clientCall.Dispose();
}
return fileOpResult.Ok ? fileOpResult.FileId.ToString() : string.Empty;
}
///
/// 下载文件
///
///
///
[HttpGet, Route("download/{fileid}")]
public async Task<FileResult> Download(long fileid)
{
var clientCall = _fileOPClient.GetFile(new FileRequest
{
FileId = fileid
});
var responseStream = clientCall.ResponseStream;
var cts = new CancellationToken();
var ms = new MemoryStream();
ms.Position = 0;
var filename = string.Empty;
var idx = 0;
try
{
//从grpc服务端分片获取文件内容
while (await responseStream.MoveNext(cts))
{
if (idx == 0)
{
filename = responseStream.Current.FileName;
}
responseStream.Current.FileData.WriteTo(ms);
idx++;
}
}
finally
{
clientCall.Dispose();
}
var data = ms.ToArray();
ms.Close();
ms.Dispose();
return File(data, "application/octet-stream", filename);
}
}
以上。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)