Kratos实现go文件上传

2023-11-18 11:08:03 浏览数 (2)

前言

一般的项目都需要文件上传,但是Kratos的官方文档并没有写明如何实现,最近项目需要我就试着自己写了一下。

原理

服务器端:WEB服务器端程序接收到“multipart/form-data”类型的HTTP请求消息后,其核心和基本的编程工作就是读取请求消息中的实体内容,然后解析出每个分区的数据,接着再从每个分区中解析出描述头和主体内容部分。

实现

方式一:手动依赖注入

通过wire依赖注入生成文件,这时可以在生成的文件中编写wire_gen.go,直接写入路径和方法,跟简单gen编写简单HTTP一样,如下所示:

代码语言:go复制
// 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调用,还有业务代码量,各位需要根据自己项目需求去选择合适的方法。

邀请人:程序员法医

0 人点赞