前言
一般的项目都需要文件上传,但是Kratos的官方文档并没有写明如何实现,最近项目需要我就试着自己写了一下。
原理
服务器端:WEB服务器端程序接收到“multipart/form-data”类型的HTTP请求消息后,其核心和基本的编程工作就是读取请求消息中的实体内容,然后解析出每个分区的数据,接着再从每个分区中解析出描述头和主体内容部分。
实现
方式一:手动依赖注入
通过wire
依赖注入生成文件,这时可以在生成的文件中编写wire_gen.go
,直接写入路径和方法,跟简单gen
编写简单HTTP一样,如下所示:
// main.go 创建 kratos 应用生命周期管理
func newApp(logger log.Logger, hs *http.Server, gs *grpc.Server, greeter *service.GreeterService) *kratos.App {
pb.RegisterGreeterServer(gs, greeter)
pb.RegisterGreeterHTTPServer(hs, greeter)
route := httpServer.Route("/")
route.POST("/v1/server/upload", greeter.Upload) // Upload方法没有在proto生成,无法使用gRPC请求
return kratos.New(
kratos.Name(Name),
kratos.Version(Version),
kratos.Logger(logger),
kratos.Server(
hs,
gs,
),
)
}
该方法可以实现文件上传,除了缺失了gRPC的功能,其他如鉴权等都不受影响。
方法二:手写proto文件
与正常的Kratos生成API方式一样,编写proto文件。
代码语言:go复制service UploadService {
// 上传文件
rpc UploadFile(stream File) returns(UploadResponse){
option (google.api.http) = {
post: "/v1/server/file/upload",
body: "*",
};
}
}
message RemoveFileRsq {
//文件路径
string fileUrl = 1;
}
message File {
bytes file = 1;
string fileName = 2;
int64 fileSize = 3;
// 其他可自定义
}
编写完文件后跟平时生成文件不一致,此时不能急于使用命令去生产文件,而是需要手写代码,如下代码。
代码语言:go复制var _ = new(context.Context)
var _ = binding.EncodeURL
const _ = http.SupportPackageIsVersion1
type UploadFileServiceHTTPServer interface {
UploadFileHttp(context.Context, *File, ...grpc.CallOption) (*UploadResponse, error)
}
func RegisterUploadFileServiceHTTPServer(s *http.Server, srv UploadFileServiceHTTPServer) {
r := s.Route("/")
r.POST("/v1/server/file/upload", _UploadService_SaveSite0_HTTP_Handler(srv))
}
func _UploadService_SaveSite0_HTTP_Handler(srv UploadFileServiceHTTPServer) func(ctx http.Context) error {
return func(ctx http.Context) error {
http.SetOperation(ctx, "/v1.system.UploadService/file/Upload")
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.UploadFileHttp(ctx, req.(*File))
})
// 逻辑处理,取数据
return ctx.Result(200, result)
}
}
注意:这边生成文件以后也要在代码中实现方法处理后续逻辑,这边最好是将文件转换成
bytes
类型,传入实现方法。
再次使用命令生成proto里面的其他API即可,其逻辑代码如下:
代码语言:go复制func (s *SystemService) UploadFileHttp(ctx context.Context, reqFile *pb.File, opts ...grpc.CallOption) (*pb.UploadResponse, error) {
log.Info("文件:%s,大小:%d, 存储:%s", reqFile.GetFileName(), reqFile.GetFileSize(), bucket)
fileName := fmt.Sprintf("%s/%s", reqFile.Directory, reqFile.FileName)
if reqFile.FileSize <= 0 {
reqFile.FileSize = int64(len(reqFile.File))
}
fileName := fmt.Sprintf("%s/%s", reqFile.Directory, reqFile.FileName)
if reqFile.FileSize <= 0 {
reqFile.FileSize = int64(len(reqFile.File))
}
err := s.minio.Save(bucket, fileName, bytes.NewReader(reqFile.File), reqFile.FileSize) // 文件上传到minio
if err != nil {
log.Error(err.Error())
}
fileType := file.CheckFileType(reqFile.FileName)
return &pb.UploadResponse{Url: fmt.Sprintf("/%v/%v", bucket, fileName), FileType: fileType}, nil
}
这里需要注意,因为上层已经将文件转换成
bytes
类型,这里无法取到文件名称,建议上传应该提前读取文件名字传入,也可随机重新生成文件名字,看业务需求!
该方法的优点就是支持gRPC,可以给其他服务调用。
总结
两个方法的差距在于是否支持gRPC调用,还有业务代码量,各位需要根据自己项目需求去选择合适的方法。
邀请人:程序员法医