Write the Code. Change the World.

9月 11

orm with 处理也轻微使用了下。现在来处理输出。处理大输出。抱怨总是包裹一层太久了,抱怨无论啥都返回 http 200 。现在要改变这个状况。

在处理这个问题之前,我们得先找到整个框架的返回是怎么实现的。其实,在之前也想到了用后置中间件来处理。看官方自己也确实是用后置中间件来统一处理返回的。我们建立一个自己的中间件,替换掉官方自己的中间件不就好了。

官方默认中间件。

import  "github.com/gogf/gf/v2/net/ghttp"
ghttp.MiddlewareHandlerResponse

之前也说过一次。gf 中间件的函数都有且仅有一个参数 r *ghttp.Request。只要实现了该方法即可。当然 r.Middleware.Next() 这个方法是要调用的。

在这个方法之前处理的逻辑,叫前置。在这个方法之后处理的逻辑,叫后置。

go 的中间件的思路和 laravel 的也是一样的。估计其他语言也是这个思路。就是前端的 axios 等的拦截器也是有着这方面的思路。

新建 resp 中间件

循序 gf 的意志。创建中间件 logic,然后通过 gf gen service 生成 service, 并绑定。最后,在想使用的地方,通过 service 来调用。之前已经实现过一次 jwt 的中间件了。继续增加一个 resp 的中间件。

internal/logic/middleware/middleware.go 后边追加

func (s *sMiddleware) Resp(r *ghttp.Request) {
    r.Middleware.Next()
}

然后调用 gf gen service,是不是在 service 中接口也对应的增加了。如果是第一次,就会生成 service 接口文件。并将绑定关系建立。

绑定关系建立

# internal/logic/logic.go

// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================

package logic

import (
    _ "goSimpleAdmin/internal/logic/middleware"
    _ "goSimpleAdmin/internal/logic/user"
)

# internal/logic/middleware/middleware.go
type (
    sMiddleware struct{}
)

func init() {
    service.RegisterMiddleware(New())
}

func New() service.IMiddleware {
    return &sMiddleware{}
}

这个是用 inti 函数的特性来处理的。

好。中间件已经建立,service 的绑定关系也确定。下边一步就是完善 resp 中间件的逻辑以及使用 resp 中间件。

完善 resp 中间件业务逻辑

把所有中间件的逻辑都放在 internal/logic/middleware/middleware.go 这里边不太好,随着中间件的增多,业务逻辑的增多。这个文件就会比较长,代码看起来就不舒服。所以 resp 这个中间件,单独建立一个文件来处理。

新建 internal/logic/middleware/resp_middleware.go 文件来处理。

建立 resp 中间件的目的是不想无论啥请求都返回 http 200 错误,啥请求都要包裹一层 code,data, message这些。 抛弃官方默认的中间件,使用自己的中间件改变这个现状。既然想请求返回的 http 状态码不再都是 200,可以先了解一下,有哪些常见 http 状态码。

其实,官方已经给出了这些状态码的。 在 net/http 库下,有个 status.go,状态常量都在里边。这里列一些出来。

StatusOK      = 200 // RFC 9110, 15.3.1  请求成功
StatusCreated = 201 // RFC 9110, 15.3.2  请求成功,并因此创建了一个新的资源

StatusUnauthorized = 401 // RFC 9110, 15.5.2  未授权,通常用于 jwt 等用户授权的情况
StatusForbidden    = 403 // RFC 9110, 15.5.4  客户端无权访问
StatusNotFound     = 404 // RFC 9110, 15.5.5  服务器找不到资源

StatusTooManyRequests = 429 // RFC 6585, 4  请求太频繁,通常用于限流

StatusInternalServerError = 500 // RFC 9110, 15.6.1  服务器遇到了不知道如何处理的情况

响应状态码可以看这里。 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status

在 api 项目中,通常也只会用到这几个状态码。 200 成功,401 未授权,403 客户端无权,404 找不到资源,429 请求太频繁,500 服务器错误。

403 客户端无权这个定义比较广泛的,比如 validate 不符合,比如业务逻辑中不符合等等都可以定义为 403。

429 这个状态码也很有用。限流的时候,请求频繁就用它。

500 所有服务器自己的错误都用 500。前端不用关心这些东西的。

这样一整合,前端代码也很好写。遇到 401 错误码,知道是要用户进行授权,要登录了。遇到 403 该提示用户请求错误了,message 中可以带上具体的 error。 遇到 429 该提示用户请求太频繁了。遇到 200 知道请求成功了,返回数据可以拿来用了。遇到 500 提示个服务端错误就可以或其他话术。这样对前端多友好。

前端在返回中间件中,对这些错误状态码进行处理该是多么好使。

好吧,那就开始做吧。

完善 internal/logic/middleware/resp_middleware.go

package middleware

import (
    "net/http"

    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/ghttp"
)

func Resp(r *ghttp.Request) {
    if r.Response.BufferLength() > 0 {
        return
    }

    var (
        httpStatus = http.StatusForbidden
        err        = r.GetError()
        res        = r.GetHandlerResponse()
    )

    status := make(map[int]int)
    status[http.StatusUnauthorized] = http.StatusUnauthorized
    status[http.StatusForbidden] = http.StatusForbidden
    status[http.StatusTooManyRequests] = http.StatusTooManyRequests
    status[http.StatusInternalServerError] = http.StatusInternalServerError

    // err 存在,错误码暂时都为 403(403,500 不处理应该都在里边,后边要分开)
    // err 存在,res 单独定义
    // err 不存在,如果有错误,用户自己应传递 status 和 message
    if err != nil {
        res = g.Map{
            "message": err.Error(),
        }
    } else {
        if r.Response.Status == 0 || r.Response.Status == http.StatusOK {
            httpStatus = http.StatusOK
        } else {
            if _, ok := status[r.Response.Status]; ok {
                httpStatus = status[r.Response.Status]
            }
        }
    }

    // 主要处理 200 401 403 429 500 这几个状态
    r.Response.WriteHeader(httpStatus)
    r.Response.WriteJson(res)
}

再来用 postman 来测试测试接口。

  • 把账号密码弄错误 (测 403 处理)
  • 登录成功 (测 200 出来)
  • 把 token 弄错 (测 401 出来)
  • 把 token 弄对 (测 200 处理)

账号密码错误,错误的 message 有了, http 状态码 403 也符合预期。

账号密码正确,token 和 expire 也返回了。http 状态码 200 也符合预期。

可是 token 错误的时候,获取用户信息的时候,不再符合我们的预期了。为什么呢,jwt 的中间件自己封装了 response,也就是遇到错误的 token,jwt 的中间件直接按照自己封装的意思,自己返回了,不再经过上边自己定义的那个中间件。

/Users/vini123/go/pkg/mod/github.com/gogf/gf-jwt/v2@v2.1.0/auth_jwt.go代码逻辑如下。

    if mw.Unauthorized == nil {
        mw.Unauthorized = func(ctx context.Context, code int, message string) {
            r := g.RequestFromCtx(ctx)
            r.Response.WriteJson(g.Map{
                "code":    code,
                "message": message,
            })
        }
    }

    func (mw *GfJWTMiddleware) middlewareImpl(ctx context.Context) {
    r := g.RequestFromCtx(ctx)

    claims, token, err := mw.GetClaimsFromJWT(ctx)
    if err != nil {
        mw.unauthorized(ctx, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, ctx))
        return
    }

    if claims["exp"] == nil {
        mw.unauthorized(ctx, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrMissingExpField, ctx))
        return
    }
    ……

不过没关系,gf-jwt 提供 Unauthorized 的回调函数了。而这个回调函数是用户可以自定义的。那么,我们在这个回调函数中按照上边的思路返回就可以。

https://goframe.org/pages/viewpage.action?pageId=6357048

之前项目已经封装过 jwt,现在把这个改一下就好了。

func Unauthorized(ctx context.Context, code int, message string) {
    r := g.RequestFromCtx(ctx)

    // 仅加了这一行
    r.Response.WriteHeader(401)
    r.Response.WriteJson(g.Map{
        "code":    code,
        "message": message + "ggggggggg",
    })
    r.ExitAll()
}

仅仅加了 r.Response.WriteHeader(401) 再用 postman 试试。

暂时,达到我们的要求了。401 已经完成。 200 也已完成。403 部分完成,500 待完成, 429 待完成。

429 等后边加一个限制频率的中间件,就可以完成。

既然后边 429 还需要返回。那何不将返回单独抽离出来。

单独抽离出 response

新建 internal/pkg/response/response.go

package response

import (
    "github.com/gogf/gf/v2/net/ghttp"
)

// 主要处理 200 401 403 429 500 这几个状态
func Json(r *ghttp.Request, status int, res interface{}) {
    r.Response.WriteHeader(status)
    r.Response.WriteJson(res)
    r.ExitAll()
}

那么之前的中间件稍微改一下。就是返回那地方改一下下。

internal/logic/middleware/resp_middleware.go

response.Json(r, httpStatus, res)

internal/pkg/jwt/jwt.go

func Unauthorized(ctx context.Context, code int, message string) {
    r := g.RequestFromCtx(ctx)
    response.Json(r, 401, g.Map{
        "message": message,
    })
}

好了,目的暂时已达到。去掉了返回时候包裹了的一层,并将错误码抽象到具体的几个错误码上。方便前端去处理。

提交版本控制。

git add .
git commit -m '去掉默认的 response 中间件,使用自定义中间件'

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注