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