大家好,我是渔夫子。本号新推出「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 mainimport ( "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,则会有如下输入:
BeginRequestLoginEndRequest
看起来执行的程序是 BeginRequest -> GetLogin -> EndRequest这样的程序。
为什么会是这个执行程序呢?其原理是因为该AuthController
实现了iris
中定义的BaseController
接口,在将GetLogin函数
转换成路由处理器时,会判断AuthController
是否实现了BaseController
接口。如果是,则转换成一个新的路由处理器,该路由处理器的逻辑是先调用BeginRequest
,而后再调用GetLogin
对应的函数,最初调用EndRequest函数
。其实当初文件iris/mvc/controller.go
的handlerOf函数
的第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 mainimport ( "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 mainimport ( "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 mainimport ( "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文档。