乐趣区

关于go:Go框架iris框架中mvc使用进阶

大家好,我是渔夫子。本号新推出「Go 工具箱」系列,意在给大家分享应用 go 语言编写的、实用的、好玩的工具。同时理解其底层的实现原理,以便更深刻地理解 Go 语言。

以下是本文的提纲,如果你对提纲中的内容曾经齐全把握,那么本文并不适宜你。否则,请持续浏览本文。

一、Controller 中的 BeforeActivation 和 AfterActivation

如果在 Controller 中定义的办法的第一个单词不是申请办法,那么该如何将该办法定义成申请处理器呢?这就须要在 Controller 中定义 BeforeActivation 办法了。该办法定义如下:

func (c *testController) BeforeActivation(b mvc.BeforeActivation) {b.Handle("GET", "/custom_path", "CustomHandler")
}

func (c *testController) CustomHandler() {fmt.Println("this is custom handler")
}

和 BeforeActivation 对应的,还有一个是 AfterActivation 函数,该函数个别用来解决给特定的处理器减少中间件等操作。如下:

func (c *testController) AfterActivation(a mvc.AfterActivation) {
    // 依据办法名 获取对应的路由
    index := a.GetRoute("GetMyHome")
    // 给该路由的处理器减少前置中间件或后置中间件
    index.Handlers = append([]iris.Handler{cacheHandler}, index.Handlers...)
}

二、Controller 中的 BeginRequest 和 EndRequest 函数

咱们当初定义一个 AuthController,并做如下路由注册:

package main

import (
    "fmt"
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/context"
    "github.com/kataras/iris/v12/mvc"
)



func main() {app := iris.New()
    // New 一次,相当于 New 了一个路由
    mvcApplication := mvc.New(app.Party("/"))

    mvcApplication.Handle(new(AuthController))

    app.Listen(":8080")
}

type AuthController struct {UserID int64}

// BeginRequest saves login state to the context, the user id.
func (c *AuthController) BeginRequest(ctx iris.Context) {ctx.Write([]byte("BeginRequest\n"))
}

func (c *AuthController) EndRequest(ctx iris.Context) {ctx.Write([]byte("EndRequest\n"))
}

func (c *AuthController) GetLogin(ctx iris.Context) {ctx.Write([]byte("Login\n"))
}

在 AuthController 中,咱们定义了两个办法 BeginRequest 和 EndRequest。注册的路由有GET /login。在浏览器中输出 http://localhost:8080/login,则会有如下输入:

BeginRequest
Login
EndRequest

看起来执行的程序是 BeginRequest -> GetLogin -> EndRequest 这样的程序。

为什么会是这个执行程序呢?其原理是因为该 AuthController 实现了 iris 中定义的 BaseController 接口,在将 GetLogin 函数 转换成路由处理器时,会判断 AuthController 是否实现了 BaseController 接口。如果是,则转换成一个新的路由处理器,该路由处理器的逻辑是先调用 BeginRequest,而后再调用GetLogin 对应的函数,最初调用 EndRequest 函数。其实当初文件iris/mvc/controller.gohandlerOf 函数 的第 453 行解决的。如下:

iris中对 BaseController 接口类型的定义如下:

type BaseController interface {BeginRequest(*context.Context)
    EndRequest(*context.Context)
}

这里须要留神的是,在 AuthController 中,BeginRequest 和 EndRequest 函数必须同时定义,即便在 EndRequest 中没有做任何事件。具体示例可参考 iris 中给出的 AuthController 的示例代码:BeginRequest 的利用示例

三、Controller 中的依赖注入

在略微简单一些的我的项目下,咱们常常会分出 service 层,让 controller 调用 service,service 再调用数据拜访层 dao。

在 controller 中咱们须要初始化 service 对象,而后调用 service 对象的相干办法。如下:

func main() {app := iris.New()
    // New 一次,相当于 New 了一个路由
    mvcApplication := mvc.New(app.Party("/"))

    mvcApplication.Handle(new(UserController))

    app.Listen(":8080")
}

type UserController struct {Ctx iris.Context}

func (c *UserController) GetBy(userId int) {service := &UserService{}
    user := service.GetUserById(userId)

    c.Ctx.Write([]byte(user))
}

type UserService struct {}

func (s *UserService) GetUserById(userId int) string {return "user"}

大家看,这里是把 UserService 对象的初始化放在了 GetBy 函数中。如果在 UserController 中有多个路由函数,那么就须要在每个函数中都须要初始化一遍 UserService 对象。

在 iris 的 MVC 框架中,能够在 UserController 的构造体中定义一个 UserService 类型的字段,在每次申请的时候,iris 会主动初始化该对象。这样就防止了在每个办法中都初始化一遍的反复代码。如下:

func main() {app := iris.New()
    // New 一次,相当于 New 了一个路由
    mvcApplication := mvc.New(app.Party("/"))

    mvcApplication.Handle(new(UserController))

    app.Listen(":8080")
}

type UserController struct {
    Ctx iris.Context
    Service *UserService
}

var u = &UserService{}

func (c *UserController) GetBy(userId int) {user := c.Service.GetUserById(userId)

    c.Ctx.Write([]byte(user))
}

type UserService struct {}

func (s *UserService) GetUserById(userId int) string {return "user"}

四、在 mvc 中应用中间件

在 iris 中,中间件能够针对全局、路由分组、特定的路由三种场景下应用。

4.1 在全局路由上应用中间件

首先,咱们定义一个中间件处理器 LogMiddleware,而后在初始化 iris 对象后,应用 Use 应用该中间件。同时,咱们初始化了两个 mvc.Application 对象 mvcUserApp 和 mvcAuthApp,别离注册了两个 Controller。那么,在拜访各自的路由时,都是先执行中间件路由 LogMiddleware 中的逻辑。

package main

import (
    "fmt"
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/context"
    "github.com/kataras/iris/v12/mvc"
)

func LogMiddleware(ctx *context.Context) {ctx.Write([]byte("I am log middle\n"))
    ctx.Next()}

func main() {app := iris.New()
    app.Use(LogMiddleware)

    mvcUserApp := mvc.New(app.Party("/user"))
    mvcUserApp.Handle(new(UserController))

    mvcAuthApp := mvc.New(app.Party("/auth"))
    mvcAuthApp.Handle(new(AuthController))

    app.Listen(":8080")
}

type UserController struct {
}

func (c *UserController) Get(Ctx iris.Context) {c.Ctx.Write([]byte(user))
}

type AuthController struct {
}

func (c *AuthController) GetLogin(ctx iris.Context) {ctx.Write([]byte("Login\n"))
}

4.2 在路由组上应用中间件

当然,咱们还能够将中间件只作用到特定的路由组上。即某个 mvc.Application 上。这里咱们须要留神下,一个 mvc.Application 下能够注册多个 Controller 的,相当于造成了一个路由组。如下,咱们在 /user 路由组的 mvcUserApp 下,再多注册一个 Controller:HomeController。同时,将中间件利用到这两个 Controller 上。这样,就有只有 UserController 和 HomeController 上的路由会应用到 LogMIddleware 中间件。如下:

package main

import (
    "fmt"
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/context"
    "github.com/kataras/iris/v12/mvc"
)

func LogMiddleware(ctx *context.Context) {ctx.Write([]byte("I am log middle\n"))
    ctx.Next()}

func main() {app := iris.New()
    app := iris.New()

    mvcApp := mvc.New(app.Party("/user"))
    // 将中间件挪动到了 mvcApp 对象上
    mvcApp.Router.Use(LogMiddleware)
    
    mvcApp.Handle(new(UserController))
    mvcApp.Handle(new(HomeController))

    // mvcAuthApp 对象将不再应用中间件
    mvcAuthApp := mvc.New(app.Party("/auth"))
    mvcAuthApp.Handle(new(AuthController))

    app.Listen(":8080")
}

type UserController struct {
}

func (c *UserController) GetBy(ctx iris.Context) {c.Ctx.Write([]byte("user"))
}


type HomeController struct {
}

func (c *HomeController) GetHomeBy(userId int) {fmt.Println("Home:", userId)
}

type AuthController struct {UserID int64}

func (c *AuthController) GetLogin(ctx iris.Context) {ctx.Write([]byte("Login\n"))
}

4.3 在特定的路由上应用中间件

将中间件应用在特定的路由上,这个在上文中咱们有提到过,就是在 Controller 的 AfterActivation 函数中。如下:

package main

import (
    "fmt"
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/context"
    "github.com/kataras/iris/v12/mvc"
)

func LogMiddleware(ctx *context.Context) {ctx.Write([]byte("I am log middle\n"))
    ctx.Next()}

func main() {app := iris.New()
    //app.Use(LogMiddleware)

    mvcApp := mvc.New(app.Party("/user"))
    mvcApp.Handle(new(UserController))
    mvcApp.Handle(new(HomeController))

    mvcAuthApp := mvc.New(app.Party("/auth"))
    mvcAuthApp.Handle(new(AuthController))

    app.Listen(":8080")
}

type UserController struct {Ctx iris.Context}

func (c *UserController) Get() {c.Ctx.Write([]byte("user"))
}


type HomeController struct {

}

func (c *HomeController) AfterActivation(a mvc.AfterActivation) {route := a.GetRoute("GetHomeBy")
    route.Handlers = append([]iris.Handler{LogMiddleware}, route.Handlers...)
}

func (c *HomeController) GetHomeBy(Ctx iris.Context, userId int) {fmt.Println("Home:", userId)
    Ctx.Writef("Home %d\n", userId)
}

五、Controller 中函数的返回值

咱们晓得,函数是能够有返回值的,而在 Go 中,函数还能够有多个返回值。那么,如果在 Controller 定义的函数中有返回值,那么 mvc 是如何解决的呢?上面咱们以 testController 中 GetHome 函数为例进行阐明。

5.1 函数返回 int 类型

当函数返回 int 类型时,mvc 包会把该值当做 http 响应的状态码来解决,返回值必须是 100 到 999 范畴的数字,否则会间接报错。如果有效值范畴内的返回值是 RFC 规定的 HTTP 响应码,那么就按对应的响应码返回给浏览器;否则,间接间接显示网页其余内容。例如,返回值是 404,则会返回网页 Not Found。而返回 10000,则间接报错。

  • 返回无效的 http 状态码:404

    func (c *testController) GetHome() int {return 404}

    输出 http://localhost:8080/home,则网页显示 Not Found 页面。

  • 返回有效的 http 状态码:10000

    func (c *testController) GetHome() int {return 10000}

    输出 http://localhost:8080/home,则网页会显示网页无奈工作,在接口侧会报 panic。

另外,如果返回的是非 int 类型的数值,比方 int64,int32 等,那么会作为网页的内容间接输入。

5.2 函数返回 bool 类型

不管函数有几个返回值,只有若返回值中有 bool 值 并且值是 false,那么,就会按 404 返回给浏览器。

5.3 函数返回 string 类型

返回 string 类型时,依据返回值的个数,有如下规定:

  • 如果只有 1 个返回值,那么返回值就作为响应体间接返回给浏览器。
  • 如果有多个 string 类型的返回值时,那么响应体中只返回最初一个返回值的内容。
  • 如果有多个 string 类型的返回值时,第二个或前面的返回值中蕴含 “/” 字符,那么,该返回值就作为响应的内容类型。例如,在 GetHome 函数中,返回了两个 string 类型的值,如下:

    func (c *testController) GetHome() (string, string) {return "application/json; charset=utf-8", "Hello World"}

    因为第一个返回值中有 “/”,这时还没有其余返回值,所以第一个返回值会被疏忽。

如果作为响应值类型的返回值在第 2 个地位,因为第一个返回的 string 值作为了响应体内容,所以,第二个返回值能力作为响应体类型。如下:

func (c *testController) GetHome() (string, string) {return "Hello World", "application/json; charset=utf-8"}

总之,个别状况下,在有多个 string 类型的返回值时,前面的值会笼罩后面的值作为响应体返回

5.4 函数返回字节数组类型

返回字节数组类型时,返回值作为响应体的内容返回给客户端。如果有多个字节数组类型,那么前面的内容会笼罩后面的内容。

例如,只返回一个字节类型的数组 []byte(“Hello”)时,那么响应内容只返回 “Hello”。如下:

func (c *testController) Get() ([]byte) {return  []byte("Hello")
}

若函数有两个字节数组类型返回值,那么只会返回最初一个字节数组的内容。如下示例只会输入 ”World”:

func (c *testController) Get() ([]byte, []byte) {return  []byte("Hello"), []byte("World")
}

如果,返回值中既有[]byte 类型,又有 string 类型,那么,前面的会笼罩后面的内容输入。

5.5 函数返回构造体或 map 类型

当函数返回值有有构造体或 map 类型时,该类型的值优先作为响应体内容输入。例如,在上面的函数中,返回值类型顺次为 Resp 类型、string 类型、[]byte 类型。那么,最终输入的内容还是 Resp 类型的值。如下:

type Resp struct {
    StatusCode int
    Message string
}

func (c *testController) Get() (Resp, string, []byte) {return  Resp{Message:"Hello World"}, string("World"), []byte("Hello")
}

5.6 函数返回 mvc.Result 类型

在 iris 的 mvc 包中,自定义了一种返回值类型:mvc.Result,该类型时一个接口,其中定义了 Dispatch 办法,如下:

type Result interface {
    // Dispatch should send a response to the client.
    Dispatch(*context.Context)
}

若要返回该类型的值,就须要实现 Dispatch 办法。而在 mvc 包中 iris 也替咱们实现了该接口:mvc.Response 类型、mvc.View 类型。通过名字也能够晓得,一个用于接口类的输入,一个用于页面的输入。如下:

type Resp struct {
    StatusCode int
    Message string
}

func (c *testController) Get() mvc.Result {
    return mvc.Response{Object: Resp{Message: "Hello World"},
    }
}

通过 mvc.View 用于页面的输入:


func (c *testController) Get() mvc.Result {
    return mvc.View{
        // Map to mvc.Code and mvc.Err respectfully on HandleHTTPError method.
        Code: iris.StatusBadRequest,
        Err:  fmt.Errorf("custom error"),
    }
}

以上就是在 iris 中应用 mvc 时输入时须要留神的中央。其输入规定的实现在源码 iris/hero/func_result.go 文件的 dispatchFuncResult 函数中,点击可查看源码:controller 中办法回调后果散发实现。

特地举荐:一个专一 go 我的项目实战、我的项目中踩坑教训及避坑指南、各种好玩的 go 工具的公众号,「Go 学堂」,专一实用性,十分值得大家关注。点击下方公众号卡片,间接关注。关注送《100 个 go 常见的谬误》pdf 文档。

退出移动版