golang gin后端開發框架(五):日志中間件和自定義response


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

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM