# 注册控制器风格的路由

所谓的注册控制器风格路由,可以理解为典型的 MVC 风格代码。即我们会在web服务中声明各式各样的Controller

具体Controller里面有什么 API,可以查看Controller API

# 基本用法

在 Beego 里面注册这种风格的路由很简单,只需要声明一个Controller就可以:


import "github.com/beego/beego/v2/server/web"

type UserController struct {
    web.Controller
}
1
2
3
4
5
6

这样我们就写好了一个Controller

如果我们要想添加一个方法,那么可以:

import "github.com/beego/beego/v2/server/web"

type UserController struct {
	web.Controller
}

func (u *UserController) HelloWorld()  {
	u.Ctx.WriteString("hello, world")
}
func main() {
	web.AutoRouter(&UserController{})
	web.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12
13

当我们访问 URL http://127.0.0.1:8080/user/helloworld的时候,可以看到结果:

hello world.png

需要注意的是,控制器里面处理http请求的方法必须是公共方法——即首字母大写,并且没有参数,没有返回值。如果你的方法不符合这个要求,大多数情况下,会发生panic,例如你的方法有参数:

func (u *UserController) HelloWorldNoPtr(name string) {
	u.Ctx.WriteString("don't work")
}
1
2
3

注意比较,在函数式注册风格里面,我们的HandleFunc其实是接收一个*Context参数的

如果你的方法接收器不是指针:

golangci-lint run

func (u UserController) HelloWorldNoPtr() {
	u.Ctx.WriteString("don't work")
}
1
2
3

这种写法也是可以的。一般的惯例是使用指针接收器,但是这并不强制。关于接收器的讨论,可以参考选择什么作为方法接收器

# Controller 的名字

在一些比较智能的 API 里面,我们会使用Controller的名字来作为前缀、命名空间等。

那么,Controller的名字是如何确定的呢?

在 Beego 里面,我们认为,一个Controller的定义是形如:

type CtrlNameController struct {

}
1
2
3

比如说,我们定义了一个UserController,那么Controller的名字就是User。如果大小写不敏感,那么user也是合法的名字。

再比如说我们定义了一个BuyerRefundController,那么BuyerRefund就是名字,大小写不敏感的时候,buyerrefund也是合法的名字。

# AutoRouter

刚才我们使用的是web模块里面一个很实用的注册路由的方法AutoRouter

AutoRouter解析出来的路由规则由RouterCaseSensitive的值,Controller的名字和方法名字共同决定。

其中UserController它的名字是User,而方法名字是HelloWorld。那么:

  • 如果RouterCaseSensitivetrue,那么AutoRouter会注册两个路由,/user/helloworld/*/User/HelloWorld/*
  • 如果RouterCaseSensitivefalse,那么会注册一个路由,/user/helloworld/*

总而言之,在使用AutoRouter的情况下,使用全小写的路径总是没错的

# AutoPrefix

AutoRouter内部是基于AutoPrefix实现的,可以说,Controller的名字,就是注册的前缀(prefix)。

下面我们来看一个简单的例子:

import (
	"github.com/beego/beego/v2/server/web"
)

type UserController struct {
	web.Controller
}

func (u *UserController) HelloWorld() {
	u.Ctx.WriteString("Hello, world")
}

func main() {
	// get http://localhost:8080/api/user/helloworld
	// you will see return "Hello, world"
	ctrl := &UserController{}
	web.AutoPrefix("api", ctrl)
	web.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在运行之后,浏览器里面输入http://localhost:8080/api/user/helloworld,就能看到返回的响应"Hello, world"。

类似于我们前面提到的AutoRoute,这里注册的路由包含:

  • 如果RouterCaseSensitivetrue,那么AutoPrefix会注册两个路由,api/user/helloworld/*api/User/HelloWorld/*

  • 如果RouterCaseSensitivefalse,那么会注册一个路由,api/user/helloworld/*

这里我们可以总结出来一般性质的规则: 当我们使用AutoPrefix的时候,注册的路由符合prefix/ctrlName/methodName这种模式。

# 手动路由

如果我们并不想利用AutoRoute或者AutoPrefix来注册路由,因为这两个都依赖于Controller的名字,也依赖于方法的名字。某些时候我们可能期望在路由上,有更强的灵活性。

在这种场景下,我们可以考虑说,采用手工注册的方式,挨个注册路由。

在 v2.0.2 我们引入了全新的注册方式。下面我们来看一个完整的例子

import (
	"github.com/beego/beego/v2/server/web"
)

type UserController struct {
	web.Controller
}

func (u *UserController) GetUserById() {
	u.Ctx.WriteString("GetUserById")
}

func (u *UserController) UpdateUser() {
	u.Ctx.WriteString("UpdateUser")
}

func (u *UserController) UserHome() {
	u.Ctx.WriteString("UserHome")
}

func (u *UserController) DeleteUser() {
	u.Ctx.WriteString("DeleteUser")
}

func (u *UserController) HeadUser() {
	u.Ctx.WriteString("HeadUser")
}

func (u *UserController) OptionUsers() {
	u.Ctx.WriteString("OptionUsers")
}

func (u *UserController) PatchUsers() {
	u.Ctx.WriteString("PatchUsers")
}

func (u *UserController) PutUsers() {
	u.Ctx.WriteString("PutUsers")
}

func main() {

	// get http://localhost:8080/api/user/123
	web.CtrlGet("api/user/:id", (*UserController).GetUserById)

	// post http://localhost:8080/api/user/update
	web.CtrlPost("api/user/update", (*UserController).UpdateUser)

	// http://localhost:8080/api/user/home
	web.CtrlAny("api/user/home", (*UserController).UserHome)

	// delete http://localhost:8080/api/user/delete
	web.CtrlDelete("api/user/delete", (*UserController).DeleteUser)

	// head http://localhost:8080/api/user/head
	web.CtrlHead("api/user/head", (*UserController).HeadUser)

	// patch http://localhost:8080/api/user/options
	web.CtrlOptions("api/user/options", (*UserController).OptionUsers)

	// patch http://localhost:8080/api/user/patch
	web.CtrlPatch("api/user/patch", (*UserController).PatchUsers)

	// put http://localhost:8080/api/user/put
	web.CtrlPut("api/user/put", (*UserController).PutUsers)

	web.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

需要注意的是,我们新的注册方法,要求我们传入方法的时候,传入的是(*YourController).MethodName。这是因为 Go 语言特性,要求在接收器是指针的时候,如果你希望拿到这个方法,那么应该用(*YourController)的形式。

那么,如果我们不用指针接收器:

import (
	"github.com/beego/beego/v2/server/web"
)

type UserController struct {
	web.Controller
}

func (u UserController) GetUserById() {
	u.Ctx.WriteString("GetUserById")
}

func (u UserController) UpdateUser() {
	u.Ctx.WriteString("UpdateUser")
}

func (u UserController) UserHome() {
	u.Ctx.WriteString("UserHome")
}

func (u UserController) DeleteUser() {
	u.Ctx.WriteString("DeleteUser")
}

func (u UserController) HeadUser() {
	u.Ctx.WriteString("HeadUser")
}

func (u UserController) OptionUsers() {
	u.Ctx.WriteString("OptionUsers")
}

func (u UserController) PatchUsers() {
	u.Ctx.WriteString("PatchUsers")
}

func (u UserController) PutUsers() {
	u.Ctx.WriteString("PutUsers")
}

func main() {

	// get http://localhost:8080/api/user/123
	web.CtrlGet("api/user/:id", UserController.GetUserById)

	// post http://localhost:8080/api/user/update
	web.CtrlPost("api/user/update", UserController.UpdateUser)

	// http://localhost:8080/api/user/home
	web.CtrlAny("api/user/home", UserController.UserHome)

	// delete http://localhost:8080/api/user/delete
	web.CtrlDelete("api/user/delete", UserController.DeleteUser)

	// head http://localhost:8080/api/user/head
	web.CtrlHead("api/user/head", UserController.HeadUser)

	// patch http://localhost:8080/api/user/options
	web.CtrlOptions("api/user/options", UserController.OptionUsers)

	// patch http://localhost:8080/api/user/patch
	web.CtrlPatch("api/user/patch", UserController.PatchUsers)

	// put http://localhost:8080/api/user/put
	web.CtrlPut("api/user/put", UserController.PutUsers)

	web.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

我们建议如果使用这个系列的方法,那么应该选择使用结构体接收器,这样代码看上去要清爽很多。

要额外引起注意的是CtrlAny方法,这意味着,任意的http方法都可以被处理。

# 注解路由

# 历史注册路由方式

和之前的注册路由方式比起来,我们这一次的改进,让用户可以在现代 IDE 中,点击方法名进行跳转。

历史上,我们的注册方式是这样的:

func main() {

	ctrl := &MainController{}

	// we register the path / to &MainController
	// if we don't pass methodName as third param
	// web will use the default mappingMethods
	// GET http://localhost:8080  -> Get()
	// POST http://localhost:8080 -> Post()
	// ...
	web.Router("/", ctrl)

	// GET http://localhost:8080/health => ctrl.Health()
	web.Router("/health", ctrl, "get:Health")

	// POST http://localhost:8080/update => ctrl.Update()
	web.Router("/update", ctrl, "post:Update")

	// support multiple http methods.
	// POST or GET http://localhost:8080/update => ctrl.GetOrPost()
	web.Router("/getOrPost", ctrl, "get,post:GetOrPost")

	// support any http method
	// POST, GET, PUT, DELETE... http://localhost:8080/update => ctrl.Any()
	web.Router("/any", ctrl, "*:Any")

	web.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

我们不再推荐使用这种方式,因为可读性和可维护性都不太好。特别是重构进行方法重命名的时候,容易出错。

# 相关内容

如何查看我注册的路由信息

控制器方法该选择哪个接收器 Controller API 路由规则——正确撰写路由