做项目。一般都会选择框架。很少会直接从 0 开始撸的。之所以选择框架,是因为框架既帮你做好了架构,又准备好了很多工具,还定制了一些规范。适合快速稳定的做项目。一般一个语言对应的开源框架会比较多,选一个或两个应用到项目中就可以了。准备进入 go 领域,也得选一个框架。初步了解了 gin 和 goframe。这里还是选择 goframe。gin 还是太轻了,项目中需要的一些工具不够多和完善。
安装升级
这里想做一个接口服务项目。仅仅提供接口给到前端使用就好。现在大多业务场景都是前后端分离的。比如app、小程序、管理后台这些。app,小程序是必须分离。管理后台也可以不分离,但分离起来更好写更爽。
按照官网文档,先安装好 gf。当前最新版本是 2.5.2。
创建项目。
gf init goSimpleAdmin
cd goSimpleAdmin
# 查看版本信息
gf -v
GoFrame CLI Tool v2.5.2, https://goframe.org
GoFrame Version: v2.3.1 in current go.mod
CLI Installed At: /Users/vini123/go/bin/gf
CLI Built Detail:
Go Version: go1.20.6
GF Version: v2.5.2
Git Commit: 2023-08-18 09:57:25 cf299273c499aafb5d19829809dfb6b8ef280c12
Build Time: 2023-08-18 16:23:18
发现 goframe 版本是 2.3.1。可以对刚创建的项目进行升级。在升级前,先做好版本控制。
git init -b main
git add .
git commit -m 'goframe initialize'
使用 gf up
对项目进行升级。
创建入口和出口
只有先定义和输入和输出,才可以用过 gf gen ctrl
来生成 controller 文件。这里就先从用户开始吧。有获取验证码、用户登录、用户注册这三个。
接口规范文档:https://goframe.org/pages/viewpage.action?pageId=47703679
按照规范。必须得在 api 目录下定义。我先把默认的 hello 删掉,再在 api 先建立一个 user
目录,再在 user
目录下建立 admin
目录,不使用 v1
目录了。默认 demo 是 v1
的。就是因为想做管理后台哈,用 admin 更适合。文件结构如下。
.
├── api
│ └── user
│ └── admin
│ └── user.go
在 user.go
中定义输入输出。定义的内容如下。
package admin
import "github.com/gogf/gf/v2/frame/g"
// 请求数字验证码
type CaptchaReq struct {
g.Meta `path:"/captcha" method:"get" tags:"CaptchaService" summary:"build captcha"`
}
type CaptchaRes struct {
Id string
}
// 登录
type SignInReq struct {
g.Meta `path:"/user/sign-in" method:"post" tags:"UserService" summary:"user sign in"`
Passport string `v:"required|length:2,64#请输入账号|账号长度为 {min} 到 {max} 位"`
Password string `v:"required|length:6,16#请输入密码|密码长度为 {min} 到 {max} 位"`
}
type SignInRes struct{}
// 注册(只需要数字验证码就可以)
type SignUpReq struct {
g.Meta `path:"/user/sign-up" method:"post" tags:"UserService" summary:"user sign up"`
Nickname string `v:"required|length:2,12#请输入昵称|昵称长度为 {min} 到 {max} 位" json:"nickname"`
Passport string `v:"required|length:2,64#请输入账号|账号长度为 {min} 到 {max} 位" json:"passport"`
Password string `v:"required|length:6,16#请输入密码|密码长度为 {min} 到 {max} 位" json:"password"`
VerifyKey string `v:"required|length:12#缺少验证KEY|验证KEY长度为 12 位" json:"verify_key"`
VerifyCode string `v:"required|length:4#请输入验证码|验证码长度为 4 位" json:"verify_code"`
}
type SignUpRes struct{}
先初步这么定义,后边也可以调整。观察下命名,都是成对的出现的。 Req
和 Res
,就是 request 和 response。感觉一切一切都离不开请求和响应。也可以说是输入和输出。念念不忘,必有回响。
结构体中, v 是用来做 validate 规则验证的标签。请看数据校验。
https://goframe.org/pages/viewpage.action?pageId=1114367
然后就可以生成 controller
了.
gf gen ctrl
# 输出以下结果。
generated: /Users/vini123/Code/zhoulin/open/goSimpleAdmin/api/user/user.go
generated: internal/controller/user/user.go
generated: internal/controller/user/user_new.go
generated: internal/controller/user/user_admin_captcha.go
generated: internal/controller/user/user_admin_sign_in.go
generated: internal/controller/user/user_admin_sign_up.go
done!
# 目录结构这样的
.
└── user
├── user.go
├── user_admin_captcha.go
├── user_admin_sign_in.go
├── user_admin_sign_up.go
└── user_new.go
# 顺便在 api/user 下,生成了一个 user.go 文件。
看看这些文件的里的内容是啥。
api/user/user.go
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package user
import (
"context"
"goSimpleAdmin/api/user/admin"
)
type IUserAdmin interface {
Captcha(ctx context.Context, req *admin.CaptchaReq) (res *admin.CaptchaRes, err error)
SignIn(ctx context.Context, req *admin.SignInReq) (res *admin.SignInRes, err error)
SignUp(ctx context.Context, req *admin.SignUpReq) (res *admin.SignUpRes, err error)
}
user_admin_captcha.go
package user
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"goSimpleAdmin/api/user/admin"
)
func (c *ControllerAdmin) Captcha(ctx context.Context, req *admin.CaptchaReq) (res *admin.CaptchaRes, err error) {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
user_admin_sign_in.go
package user
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"goSimpleAdmin/api/user/admin"
)
func (c *ControllerAdmin) SignIn(ctx context.Context, req *admin.SignInReq) (res *admin.SignInRes, err error) {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
user_admin_sign_up
package user
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"goSimpleAdmin/api/user/admin"
)
func (c *ControllerAdmin) SignUp(ctx context.Context, req *admin.SignUpReq) (res *admin.SignUpRes, err error) {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
user_new.go
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package user
import (
"goSimpleAdmin/api/user"
)
type ControllerAdmin struct{}
func NewAdmin() user.IUserAdmin {
return &ControllerAdmin{}
}
user.go
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package user
以上这些代码,都是框架生成的。是不是像介绍说的一样,很方便,也是不是都统一规范了。这还只是生成一个 controller。下边来生成 dao 和 model 文件。
配置业务使用的数据库以及工具使用的数据库
dao 和 model 文件是可以和上边的操作并行的。在创建方面,和上边的操作没有联系没有耦合。但在意义和使用上是有联系的。创建 dao 和 model 必须得先连数据库。那就得先配置。
goframe 将工具配置和业务配置分开的。
之所以要先配置数据库信息。是因为在使用 gf gen dao
的时候需要用到。既然使用框架,既然有这么好(方便、统一规范)的 cli 工具。那就要用上。
配置管理:https://goframe.org/pages/viewpage.action?pageId=57183754
工具配置:https://goframe.org/pages/viewpage.action?pageId=1114260
工具配置文件路径优先查找 hack 目录(hack/config.yaml),其次,按照开光家默认的配置路径检索配置文件。把数据库配置起来。
# hack/config.yaml
# CLI tool, only in development environment.
# https://goframe.org/pages/viewpage.action?pageId=3673173
gfcli:
docker:
build: "-a amd64 -s linux -p temp -ew"
tagPrefixes:
- my.image.pub/my-app
gen:
dao:
- link: "mysql:homestead:secret@tcp(192.168.56.56:3306)/go-simple-admin"
tables: "user"
就先配置这么多。这个表可以随时添加的和修改的。执行 gf gen dao
的时候,就会连接数据库,根据表的信息生成 dao 和 model 文件。这个后边再说。
再来配置业务数据库以及端口这些。
业务配置文件存放于 manifest/config
目录下, 业务配置文件往往不提交到版本库中。觉得这块应该搞个像 .env.example 这样的东西出来。
server:
address: ":9885"
openapiPath: "/api.json"
swaggerPath: "/swagger"
logger:
path: "storage/logs/daily" # 日志文件路径。默认为空,表示关闭,仅输出到终端
file: "{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log"
prefix: "" # 日志内容输出前缀。默认为空
level: "all" # 日志输出级别
timeFormat: "2006-01-02T15:04:05" # 自定义日志输出的时间格式,使用Golang标准的时间格式配置
ctxKeys: [] # 自定义Context上下文变量名称,自动打印Context的变量到日志中。默认为空
header: true # 是否打印日志的头信息。默认true
stdout: true # 日志是否同时输出到终端。默认true
rotateSize: 0 # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性
rotateExpire: 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性
rotateBackupLimit: 0 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupExpire: 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupCompress: 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩
rotateCheckInterval: "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时
stdoutColorDisabled: false # 关闭终端的颜色打印。默认开启
writerColorEnable: false # 日志文件是否带上颜色。默认false,表示不带颜色
logger1:
path: "storage/losgs/admin/"
file: "{Y-m-d}.log"
stdout: true
database:
logger:
path: "storage/logs/mysql"
level: "all"
stdout: true
default:
link: "mysql:homestead:secret@tcp(192.168.56.56:3306)/go-simple-admin"
debug: true
这里不仅配置了服务的端口信息,还配置了日志和数据库信息。
生成 dao 文件和 model 文件
❯ gf gen dao
generated: internal/dao/user.go
generated: internal/dao/internal/user.go
generated: internal/model/do/user.go
generated: internal/model/entity/user.go
done!
dao 和 model 文件,可以一直重复生成,生的样子是和数据库里的表相关的。当然也和工具配置相关。暂时,只配置了一个 user 表,生成了四个文件。
├── dao
│ ├── internal
│ │ └── user.go
│ └── user.go
├── model
│ ├── do
│ │ └── user.go
│ └── entity
│ └── user.go
下边来生成 service 文件
生成 service 文件,是由 logic 文件生成。换句话说,就是先写好 logic 文件,再去生成 service 文件。 logic 文件是啥,logic 才是业务逻辑真正的地方。在生成 service 之前,可以先完成 logic 的雏形。logic 会用到具体的 model,这里我们新建一个具体的 model。
# model/admin/user.go
package admin
type SigninReq struct{}
type SigninRes struct{}
以用户登录为例。
package user
import (
"context"
"goSimpleAdmin/internal/model"
)
type sUser struct{}
func init() {
}
func New() {
}
func (s *sUser) SignIn(ctx context.Context, in model.SignInInput) (err error) {
return nil
}
先就写上边这么多,是个引子(控制器想要调用的方法)。然后执行。
❯ gf gen service
generating service go file: internal/service/user.go
generating init go file: internal/logic/logic.go
gofmt go files in "internal/service"
update main.go
done!
生成好 service 后,再回过头来完善 logic。
package user
import (
"context"
"goSimpleAdmin/internal/model"
"goSimpleAdmin/internal/service"
)
type sUser struct{}
func init() {
service.RegisterUser(New())
}
func New() service.IUser {
return &sUser{}
}
func (s *sUser) SignIn(ctx context.Context, in model.SignInInput) (err error) {
return nil
}
在 logic 文件的 init 函数中,将 service 的接口对象 和 logic 进行绑定。绕来绕去,控制器调用的就是 logic 中的方法。通过 logic 中的方法,来生成 service,就是对应 logic 的接口。
搞来搞起,搞了上边那么多。现在关键时候来了。让控制器把服务调用起来。
修改 user_admin_sign_in.go
package user
import (
"context"
"goSimpleAdmin/api/user/admin"
"goSimpleAdmin/internal/model"
"goSimpleAdmin/internal/service"
)
func (c *ControllerAdmin) SignIn(ctx context.Context, req *admin.SignInReq) (res *admin.SignInRes, err error) {
service.User().SignIn(ctx, model.SignInInput{
Passport: req.Passport,
Password: req.Passport,
})
return
}
业务是都串联起来了。那么入口呢。 入口就在路由的绑定上,要把 controller 中的 user 对象绑定到路由上。修改 cmd/cmd.go
package cmd
import (
"context"
"goSimpleAdmin/internal/controller/user"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/api/admin", func(group *ghttp.RouterGroup) {
group.Bind(
user.NewAdmin(),
)
})
s.Run()
return nil
},
}
)
绑也绑了,串也串了。运行一下下看。
go run main.go
❯ go run main.go
2023-09-06 19:57:38.553 [INFO] pid[56394]: http server started listening on [:9885]
2023-09-06 19:57:38.553 [INFO] {20fa99f1594d82178bf09e11e95f3c4b} swagger ui is serving at address: http://127.0.0.1:9885/swagger/
2023-09-06 19:57:38.554 [INFO] {20fa99f1594d82178bf09e11e95f3c4b} openapi specification is serving at address: http://127.0.0.1:9885/api.json
ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
----------|--------|--------------------------------------|---------------------------------------------------------------------------------------|--------------------
:9885 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE
----------|--------|--------------------------------------|---------------------------------------------------------------------------------------|--------------------
:9885 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.MiddlewareHandlerResponse | GLOBAL MIDDLEWARE
----------|--------|--------------------------------------|---------------------------------------------------------------------------------------|--------------------
:9885 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec |
----------|--------|--------------------------------------|---------------------------------------------------------------------------------------|--------------------
:9885 | GET | /api/admin/captcha | goSimpleAdmin/internal/controller/user.(*ControllerAdmin).Captcha |
----------|--------|--------------------------------------|---------------------------------------------------------------------------------------|--------------------
:9885 | POST | /api/admin/user/sign-in | goSimpleAdmin/internal/controller/user.(*ControllerAdmin).SignIn |
----------|--------|--------------------------------------|---------------------------------------------------------------------------------------|--------------------
:9885 | POST | /api/admin/user/sign-up | goSimpleAdmin/internal/controller/user.(*ControllerAdmin).SignUp |
----------|--------|--------------------------------------|---------------------------------------------------------------------------------------|--------------------
:9885 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE
----------|--------|--------------------------------------|---------------------------------------------------------------------------------------|--------------------
因为只写了 sign-in 的调用。就先用 postman
试试 sign-in 吧。
下边这个图,仅仅是以 post 方式调用接口。没传任何参数。返回 code 51, 以及 message 请输入账号。请注意看 status code 是 200。
再看这张图,账号按要求输入了,密码达不到要求。返回 code 51, 以及 message 密码长度为 6 和 16 位。请注意看 status code 是 200。
最后看这张图,账号密码要求都达到了。返回 code 0,以及 message 位空, data 为 null 。请注意看 status code 是 200。
到这里,流程是通了。为什么 data 是 null 呢。在 logic 中,我啥逻辑都没写,就返回个 nil。到这里成为 null 了。
发现一个问题,无论 validate 错误还是正常返回,状态码都是 200。并且返回的数据都包裹了一层。要是以以往的习惯,有错误返回的都不会是 200。一般是 403 这样的。包裹一层也有点难受。 包裹的 code 码,对于用户,对于前端来说也是毫无意义。
先这么弄吧。
下边,要把注册以及图片验证码 logic 和 service 完善起来。再在 controller 中调用起来。
提交版本控制。
git add .
git commit -m '用户登录注册相关的 api、ctrl、model、dao、logic 的生成以及串联'
到此,基础的进口出口已经串起来了。剩下就是业务逻辑填充。这里,主要是数据库 ORM 的操作。