嘿,朋友!欢迎来到后端编程的奇妙世界。今天我们要干一件所有伟大项目开始时都要干的事——搞定“用户”。毕竟,一个没有用户的网站,就像一场没有客人的派对,只能自己跟自己玩,多尴尬。
一个项目开始,咱们通常先搞定用户的“两大基本需求”:怎么进来(注册/登录)和进来之后怎么折腾(查看/修改个人信息)。
1 搭个“毛坯房”并开几个“临时门”
想象一下,我们现在要盖一栋互联网大厦。main函数就是我们这栋大厦的入口大厅。我们用 Go 语言里一个叫 Gin 的神奇工具来快速搭建一个框架。
1
2
3
4
5
6
7
8
9
10
11
12
|
// main.go
package main
import "github.com/gin-gonic/gin"
func main() {
// 召唤 Gin,我们的建筑大师!
server := gin.Default()
// 告诉大厦开始营业,在前台的 8080 端口竖起耳朵听着
server.Run(":8080")
}
|
好了,大厦的架子有了。现在,我们需要为用户开几个门,让他们能进来办事。
- 注册 (
/users/signup)
- 登录 (
/users/login)
- 修改资料 (
/users/edit)
- 查看个人主页 (
/users/profile)
最简单粗暴的方法,就是在大厅里扯着嗓子喊:
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
|
// main.go (一个非常“热情”的大厅)
func main() {
server := gin.Default()
// “注册的来这边排队!”
server.POST("/users/signup", func(context *gin.Context) {
// 这里处理注册逻辑...
})
// “登录的往这边走!”
server.POST("/users/login", func(context *gin.Context) {
// 这里处理登录逻辑...
})
// “改资料的在这填表!”
server.POST("/users/edit", func(context *gin.Context) {
// 这里处理修改逻辑...
})
// “想看自己长啥样的,看这边镜子!”
server.GET("/users/profile", func(context *gin.Context) {
// 这里处理查看逻辑...
})
server.Run(":8080")
}
|
代码能跑吗?能!但是你想象一下那个场景:大厅里挤满了人,负责注册的、负责登录的、负责打印资料的全都挤在一个柜台后面。新来的程序员同事看到这段代码,表情大概是这样的:(╯°□°)╯︵ ┻━┻
这不行,太乱了!我们得成立专门的部门来处理专门的事。
2 成立“用户部”,让专业的人干专业的事
我们不能让大厅(main函数)变成菜市场。所以,我们来成立一个“用户部”(User Department),在代码里,它叫 UserHandler,我打算在其之上定义用户相关的路由。
我们先在 web/user.go 文件里定义好这个部门和它的员工。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// web/user.go
package web
import "github.com/gin-gonic/gin"
// UserHandler 就是我们的“用户部”
type UserHandler struct{}
// SignUp 是用户部的“注册专员”
func (u *UserHandler) SignUp(ctx *gin.Context) {
// ...具体的注册业务逻辑放这里
}
// Login 是“登录接待员”
func (u *UserHandler) Login(c *gin.Context) {}
// Edit 是“信息修改顾问”
func (u *UserHandler) Edit(c *gin.Context) {}
// Profile 是“个人形象展示员”
func (u *UserHandler) Profile(c *gin.Context) {}
|
现在,部门和员工都有了。大厅(main函数)就不用那么乱了,它的工作变得非常简单:当个前台总机,负责接电话和转接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// main.go (一个清爽的大厅)
import "your-project/web" // 别忘了导入我们的“用户部”
func main() {
server := gin.Default()
// 聘请“用户部”的经理 u
u := &web.UserHandler{}
// 在前台总机设置转接规则
server.POST("/users/signup", u.SignUp) // 注册请求?好的,转接给用户部的注册专员!
server.POST("/users/login", u.Login) // 登录?转给登录接待员!
server.POST("/users/edit", u.Edit) // 改资料?信息修改顾问等着您!
server.GET("/users/profile", u.Profile) // 看主页?形象展示员马上服务!
server.Run(":8080")
}
|
看到没?main函数现在干净多了!它只负责“指路”,不负责具体办事。
为啥可以这么丝滑地转接?
server.POST 需要一个能处理网络请求的“员工”。而我们的 u.SignUp 方法,它的“简历”(函数签名)写得明明白白:func(ctx *gin.Context),这完全符合 Gin 总部的招聘要求!所以可以直接把它派过去干活。这就是函数式编程的魅力,函数本身可以像个“零件”一样被传来传去。
3 前台总机的“工作手册”也太厚了!
好了,大厅是干净了。但一个新的问题来了。如果我们的网站以后不止有“用户部”,还有“商品部”、“订单部”、“财务部”……那前台总机的这本“转接手册”(main函数里的路由注册代码)岂不是要写几百上千行?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// main.go (一个未来可能累死的前台)
func main() {
server := gin.Default()
// 用户部的转接规则... (4行)
u := &web.UserHandler{}
server.POST("/users/signup", u.SignUp)
// ...
// 商品部的转接规则... (100行)
p := &web.ProductHandler{}
// ...
// 订单部的转接规则... (200行)
o := &web.OrderHandler{}
// ...
server.Run(":8080")
}
|
前台小姐姐要哭了!我们得想办法精简她的工作手册。
方案A:成立一个“路由登记处”
我们可以创建一个叫 RegisterRoutes 的函数,把所有部门的转接规则都写到这个函数里。main函数只需要喊一嗓子:“喂!路由登记处,上班了!”就行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// web/main.go
package web
func RegisterRoutes() *gin.Engine {
server := gin.Default()
// 用户部来登记
u := &UserHandler{}
server.POST("/users/signup", u.SignUp)
// ...
// 商品部也来登记
// ...
return server
}
// main.go
func main() {
// “路由登记处,快把今天的转接规则弄好给我!”
server := web.RegisterRoutes()
server.Run(":8080")
}
|
好一点了!main函数彻底解放了。但是 RegisterRoutes 成了新的“菜市场”,还是会变得巨大无比。
方案B:给“路由登记处”内部分组
既然一个登记员忙不过来,那就多找几个!一个负责登记用户部的,一个负责登记商品部的。
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
|
// web/routes.go
// 总登记处
func RegisterRoutes() *gin.Engine {
server := gin.Default()
// “小张,去把用户部的路由登记一下”
registerUserRoutes(server)
// “小李,去搞定商品部的”
registerGoodsRoutes(server)
return server
}
// 用户部专用登记员
func registerUserRoutes(server *gin.Engine) {
u := &UserHandler{}
server.POST("/users/signup", u.SignUp)
// ...
}
// 商品部专用登记员
func registerGoodsRoutes(server *gin.Engine) {
// ...
}
|
这个方案好多了!职责分明,代码清晰。很多人都这么干,完全没问题!
4 我的终极方案——“部门自治”
上面方案B已经很不错了,但我个人还有个更“懒”的玩法,我称之为“部门自治”。
我不设一个中央的“路由登记处”,而是让每个部门自己负责登记自己的路由。
就像这样,我给“用户部” UserHandler 增加一个新能力:RegisterRoutes。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// web/user.go
type UserHandler struct {
}
// 让用户部自己告诉总机系统,自己的电话该怎么转
func (u *UserHandler) RegisterRoutes(server *gin.Engine) {
// 为了方便管理,用户部的所有电话都加个 "/users" 前缀
ug := server.Group("/users")
ug.POST("/signup", u.SignUp) // 注意,这里路径变短了,因为自带了 /users 前缀
ug.POST("/login", u.Login)
ug.POST("/edit", u.Edit)
ug.GET("/profile", u.Profile)
}
func (u *UserHandler) SignUp(ctx *gin.Context) { /* ... */ }
// ... 其他方法
|
现在,main函数想接入“用户部”的业务,只需要:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// main.go
func main() {
server := gin.Default()
// 喊各个部门自己来总机登记
u := &web.UserHandler{}
u.RegisterRoutes(server)
p := &web.ProductHandler{}
p.RegisterRoutes(server) // 商品部也自己来登记
server.Run(":8080")
}
|
为啥我偏爱这种“自治”模式?
- 避免打架:当好几个程序员同时修改一个巨大的中央“路由登记文件”时,代码冲突简直是家常便饭。现在好了,你改你的“用户部”,我改我的“商品部”,咱们井水不犯河水。
- 权责清晰:所有和用户相关的接口定义,都封装在
user.go 这一个文件里。找问题、加功能,一步到位,不用在好几个文件里跳来跳去。
当然,它也有个小缺点:
- 缺乏全局视野:你无法在一个文件里看到整个项目的所有接口。有时候可能会不小心定义出重复的路由(比如两个部门都想用
/api/v1/list)。但这需要团队通过沟通和规范来避免。
好了,今天的“总机安装教程”就到这里。我们从一个乱糟糟的大厅开始,一步步优化,最终实现了一个清爽、有序、可扩展的“部门自治”路由系统。
编程的乐趣就在于此:先让它跑起来,然后再想办法让它跑得更优雅! 快去试试吧!