起因

在我们使用 kratos 开发 web 应用时,由于 kratos 默认设计导致我们在自定义错误时,访问 api 时,相应的状态码是以自定义的 error code 作为 http status code 表现的。

如果我们想将 业务状态码和 HTTP 状态码分离,即所有的 http status code 都为 200,实际的业务状态码以在 response 中响应的 code 为主。

我们该如何实现呢?有 2 种方法,一种是自定义中间件将错误进行特别处理,另一种是自定义 Encoder。本篇文章主要就是介绍 Encoder 方法。

自定义 Encoder

根据我们的需要,我们需要自定义 2 个 Encoder, ErrorEncoderResponseEncoder 函数。需要的参数类型分别如下:

// ResponseEncoder with response encoder.
func ResponseEncoder(en EncodeResponseFunc) ServerOption {
    return func(o *Server) {
        o.enc = en
	}
}

// ErrorEncoder with error encoder.
func ErrorEncoder(en EncodeErrorFunc) ServerOption {
    return func(o *Server) {
        o.ene = en
    }
}
// EncodeResponseFunc is encode response func.
type EncodeResponseFunc func(http.ResponseWriter, *http.Request, interface{}) error

// EncodeErrorFunc is encode error func.
type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)

1 自定义 Response 结构 定义一个 Response 的结构体

type Response struct {
	Code    int         `json:"code" form:"code" protobuf:"varint,1,opt,name=code"`
	Message string      `json:"message" form:"message" protobuf:"bytes,2,opt,name=message"`
	Ts      string      `json:"ts" form:"ts" protobuf:"bytes,3,opt,name=ts"`
	Reason  string      `json:"reason" form:"reason" protobuf:"bytes,4,opt,name=reason"`
	Data    interface{} `json:"data" form:"data" protobuf:"bytes,5,opt,name=data"`
}

2 定义 2 个 Encoder 函数,分别对应 Response 和 Error 的编码

func ErrorEncoder(w stdhttp.ResponseWriter, r *stdhttp.Request, err error) {
	se := errors.FromError(err)
	reply := NewResponse()
	reply.Code = int(se.Code)
	reply.Data = nil
	reply.Message = se.Message
	reply.Reason = se.Reason
	reply.Ts = time.Now().Format("2006-01-02 15:04:05.00000")

	codec, _ := http.CodecForRequest(r, "Accept")
	body, err := codec.Marshal(reply)
	if err != nil {
		w.WriteHeader(stdhttp.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", contentType(codec.Name()))
	w.WriteHeader(stdhttp.StatusOK)
	w.Write(body)
}

func ResponseEncoder(w stdhttp.ResponseWriter, r *stdhttp.Request, v interface{}) error {
	reply := NewResponse()
	reply.Code = 200
	reply.Data = v
	reply.Message = "success"
	reply.Reason = "success"
	reply.Ts = time.Now().Format("2006-01-02 15:04:05.00000")

	codec, _ := http.CodecForRequest(r, "Accept")
	data, err := codec.Marshal(reply)
	if err != nil {
		return err
	}
	w.Header().Set("Content-Type", contentType(codec.Name()))
	w.WriteHeader(stdhttp.StatusOK)
	w.Write(data)
	return nil
}

3 在 http server 添加相应的 Encoder

	opts = append(opts, http.ErrorEncoder(Encoder.ErrorEncoder))
	opts = append(opts, http.ResponseEncoder(Encoder.ResponseEncoder))

4 运行代码,访问相应接口进行测试:

img1.png

img_1.png

参考