1. 自定义response
对于具体的项目而言,我们需要基于JSON()自定义一个方便好用的response
比如下面这种形式:
type Response struct { StatusCode int `json:"status_code"` // 业务状态码 Message string `json:"message"` // 提示信息 Data interface{} `json:"data"` // 任何数据 Meta Meta `json:"meta"` // 源数据,存储比如请求ID、分页信息等 Errors []ErrorItem `json:"errors"` // 错误提示,比如xx字段不能为空等 } type Meta struct { RequestID string `json:"request_id"` Page int `json:"page"` } type ErrorItem struct { Key string `json:"key"` Value string `json:"value"` } func NewResponse() *Response{ return &Response{ StatusCode: 200, Message: "success", Data: nil, Meta: Meta{ RequestID: "1234", // 可以是uuid Page: 1, }, Errors: []ErrorItem{}, } }
同时我们封装gin.Context来做一些便捷的返回response的操作
// Wrapper 封装了gin.Context type Wrapper struct { ctx *gin.Context } func WrapContext(ctx *gin.Context) *Wrapper { return &Wrapper{ctx: ctx} } // Success 输出成功信息 func (wrapper *Wrapper) Success(data interface{}) { resp := NewResponse() resp.Data = data wrapper.ctx.JSON(http.StatusOK, resp) } // Error 输出错误信息 func (wrapper *Wrapper) Error(statusCode int, errMessage string) { resp := NewResponse() resp.StatusCode = statusCode resp.Message = errMessage wrapper.ctx.JSON(statusCode, resp) }
现在就可以使用封装gin.Context的自定义结构体Wrapper来做响应的返回了:
func main() { router := gin.Default() router.GET("/", func(ctx *gin.Context) { WrapContext(ctx).Success("hello world") }) router.Run() }
2. 日志中间件
我们有时候需要一些日志来判断做一些错误处理,虽然gin已经默认使用了一个很不错的中间件,但可能我们需要的信息并不在其中
下面我们自定义一个日志中间件,首先应该明确我们在日志中应该记录什么?
一般的日志中间件就会记录这些信息:请求头、响应体、响应时间、请求方法、请求IP等
首先实现一个方法来请求体:
func getRequestBody(ctx *gin.Context) interface{} { switch ctx.Request.Method { case http.MethodGet: return ctx.Request.URL.Query() case http.MethodPost: fallthrough case http.MethodPut: fallthrough case http.MethodPatch: var bodyBytes []byte bodyBytes, err := ioutil.ReadAll(ctx.Request.Body) if err != nil { return nil } ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) return string(bodyBytes) } return nil }
我们需要定义一个结构体和方法
// bodyLogWriter 定义一个存储响应内容的结构体 type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } // Write 读取响应数据 func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) }
在bodyLogWriter结构体中封装了gin的responseWriter,然后在重写的Write方法中,首先向bytes.Buffer中写数据,然后响应
这保证了我们可以正确的获取到响应内容
最后就是中间件的实现,其中最重要的一点就是用我们自定义的bodyLogWriter来代替ctx.Writer,保证响应会保留一份在bytes.Buffer中
// RequestLog gin请求日志中间件 func RequestLog(ctx *gin.Context) { t := time.Now() // 初始化bodyLogWriter blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer, } ctx.Writer = blw // 获取请求信息 requestBody := getRequestBody(ctx) ctx.Next() // 记录响应信息 // 请求时间 costTime := time.Since(t) // 响应内容 responseBody := blw.body.String() // 日志格式 logContext := make(map[string]interface{}) logContext["request_uri"] = ctx.Request.RequestURI logContext["request_method"] = ctx.Request.Method logContext["refer_service_name"] = ctx.Request.Referer() logContext["refer_request_host"] = ctx.ClientIP() logContext["request_body"] = requestBody logContext["request_time"] = t.String() logContext["response_body"] = responseBody logContext["time_used"] = fmt.Sprintf("%v", costTime) logContext["header"] = ctx.Request.Header log.Println(logContext) }
当然最后日志在控制台打印了,如果有持久化的需求可以异步持久化到本地或者远程的数据库
3. 跨域中间件
func CORSMiddleWare(ctx *gin.Context) { method := ctx.Request.Method // set response header ctx.Header("Access-Control-Allow-Origin", ctx.Request.Header.Get("Origin")) ctx.Header("Access-Control-Allow-Credentials", "true") ctx.Header("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") ctx.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") // 默认过滤options和head这两个请求,使用204返回 if method == http.MethodOptions || method == http.MethodHead { ctx.AbortWithStatus(http.StatusNoContent) return } ctx.Next() }