go-kratos / kratos

Your ultimate Go microservices framework for the cloud-native era.
https://go-kratos.dev
MIT License
23.43k stars 4.01k forks source link

[Question]业务错误的状态码不想和http status混了,想自定义怎么办? #1281

Closed majintao closed 3 years ago

majintao commented 3 years ago

比如:业务状态码10003,请求http-status 又要是200 的情况

shenqidebaozi commented 3 years ago

可以在 errorsEncoder 处理

shenqidebaozi commented 3 years ago

应该也可以 errors.New(200, reason,msg)

tonybase commented 3 years ago

可以自定义错误,例如: errors.New(10003, message)

chenyue233 commented 3 years ago

我是定义了统一的http返回结构体

type Response struct {
    Code    int         `json:"code" form:"code"`
    Message string      `json:"message" form:"message"`
    Ts      string      `json:"ts" form:"ts"`
    Reason  string      `json:"reason" form:"reason"`
    Data    interface{} `json:"data" form:"data"`
}

然后重写 ErrorEncoder

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(pkgTime.MilliTimeLayout)

    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)
}

再重写 ResponseEncoder

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(pkgTime.MilliTimeLayout)

    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
}

最后注入到http server中

    // 错误解码器
    opts = append(opts, http.ErrorEncoder(Encoder.ErrorEncoder))
    // 返回参数解码器
    opts = append(opts, http.ResponseEncoder(Encoder.ResponseEncoder))
akalittle commented 3 years ago

@chenyue233
能贴一下完整的demo么。。有几个函数没有

chenyue233 commented 3 years ago

@chenyue233 能贴一下完整的demo么。。有几个函数没有

pkgTime.MilliTimeLayout 是 2006-01-02 15:04:05.00000 , stdHttp.StatusInternalServerError 是标准库的,contentType(codec.Name())是从kratos的httputils拷出来的 具体内容是 func ContentType(subtype string) string { return strings.Join([]string{baseContentType, subtype}, "/") }。我是重写业务需要和之前的接口保持一致,不是最佳实践。

blingblingdev commented 3 years ago

我是定义了统一的http返回结构体

type Response struct {
  Code    int         `json:"code" form:"code"`
  Message string      `json:"message" form:"message"`
  Ts      string      `json:"ts" form:"ts"`
  Reason  string      `json:"reason" form:"reason"`
  Data    interface{} `json:"data" form:"data"`
}

然后重写 ErrorEncoder

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(pkgTime.MilliTimeLayout)

  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)
}

再重写 ResponseEncoder

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(pkgTime.MilliTimeLayout)

  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
}

最后注入到http server中

  // 错误解码器
  opts = append(opts, http.ErrorEncoder(Encoder.ErrorEncoder))
  // 返回参数解码器
  opts = append(opts, http.ResponseEncoder(Encoder.ResponseEncoder))

这样子做会有一个问题,omitempty在json的序列化中会生效,导致默认值的数据返回并不完整

shcw commented 2 years ago

我是定义了统一的http返回结构体

type Response struct {
  Code    int         `json:"code" form:"code"`
  Message string      `json:"message" form:"message"`
  Ts      string      `json:"ts" form:"ts"`
  Reason  string      `json:"reason" form:"reason"`
  Data    interface{} `json:"data" form:"data"`
}

然后重写 ErrorEncoder

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(pkgTime.MilliTimeLayout)

  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)
}

再重写 ResponseEncoder

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(pkgTime.MilliTimeLayout)

  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
}

最后注入到http server中

  // 错误解码器
  opts = append(opts, http.ErrorEncoder(Encoder.ErrorEncoder))
  // 返回参数解码器
  opts = append(opts, http.ResponseEncoder(Encoder.ResponseEncoder))

@chenyue233 这个样子的话 openapi 是怎么处理的呀 ?

majintao commented 1 year ago

应该也可以 errors.New(200, reason,msg)

grpc传输这个 errors 200;client端会接受错误 打印EOF错误

kratos-ci-bot commented 1 year ago

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


should also work errors.New(200, reason, msg)

grpc transmits this errors 200; the client side will accept the error and print EOF error

majintao commented 1 year ago

可以自定义错误,例如: errors.New(10003, message)

状态码设置 不能大于600;因为这里映射的是htp的状态码

kratos-ci-bot commented 1 year ago

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Errors can be customized, for example: errors.New(10003, message)

The status code setting cannot be greater than 600; because the status code of http is mapped here

xiak commented 11 months ago

@chenyue233 能贴一下完整的demo么。。有几个函数没有

把上面那个朋友的代码整理了下, 记录一下

internal/server/http.go

func NewHTTPServer(c *conf.Server, ac *conf.Auth, logger log.Logger, s *service.PassportLoginService) *http.Server {
    var opts = []http.ServerOption{
        http.Middleware(
            recovery.Recovery(),
            logging.Server(logger),
        ),
        http.Filter(handlers.CORS(
            handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
            handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}),
            handlers.AllowedOrigins([]string{"*"}),
        )),
    }
    if c.Http.Network != "" {
        opts = append(opts, http.Network(c.Http.Network))
    }
    if c.Http.Addr != "" {
        opts = append(opts, http.Address(c.Http.Addr))
    }
    if c.Http.Timeout != nil {
        opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
    }
        // 这里加了两行==============================
    opts = append(opts, http.ResponseEncoder(ResponseEncoder))
    opts = append(opts, http.ErrorEncoder(ErrorEncoder))
    srv := http.NewServer(opts...)
    v1.RegisterHTTPServer(srv, s)
    return srv
}

新建一个 http_encoder.go 文件 internal/server/http_encoder.go

package server

import (
    "net/http"
    "strings"
    "time"

    "github.com/go-kratos/kratos/v2/errors"
    khttp "github.com/go-kratos/kratos/v2/transport/http"
)

type Response struct {
    Code    int         `json:"code" form:"code"`
    Message string      `json:"message" form:"message"`
    Ts      string      `json:"ts" form:"ts"`
    Data    interface{} `json:"data" form:"data"`
}

type ErrResponse struct {
    Code    int         `json:"code" form:"code"`
    Message string      `json:"message" form:"message"`
    Ts      string      `json:"ts" form:"ts"`
    Reason  string      `json:"reason" form:"reason"`
    Data    interface{} `json:"data" form:"data"`
}

func ErrorEncoder(w http.ResponseWriter, r *http.Request, err error) {
    se := errors.FromError(err)
    reply := &ErrResponse{}
    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, _ := khttp.CodecForRequest(r, "Accept")
    body, err := codec.Marshal(reply)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", strings.Join([]string{"application", codec.Name()}, "/"))
    w.WriteHeader(http.StatusOK)
    w.Write(body)
}

func ResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
    reply := &Response{}
    reply.Code = 0
    reply.Data = v
    reply.Message = "ok"
    reply.Ts = time.Now().Format("2006-01-02 15:04:05.00000")

    codec, _ := khttp.CodecForRequest(r, "Accept")
    data, err := codec.Marshal(reply)
    if err != nil {
        return err
    }
    w.Header().Set("Content-Type", strings.Join([]string{"application", codec.Name()}, "/"))
    w.WriteHeader(http.StatusOK)
    w.Write(data)
    return nil
}