亲手写一个gin server项目-3路由及目录组织
序
本系列文章旨在从头到尾写一个admin的gin server项目。
整个项目包括:项目结构,项目启动,配置文件,中间件,日志,格式化响应,表单校验,检索,jwt会话,rbac鉴权,单元测试,文档生成,基于es的单体日志采集,基于prometheus的指标监控......。
所涉及到的技术栈包括:gin、zap、mysql、gorm、redis、es、prometheus 等。
今天介绍路由及目录组织。
项目目录
前面为了方便讲解和代码复现,所有的代码都在一个文件里。
写的handler多了,接口多了,都在一个文件里肯定不合适的。
下面是一个完整的项目目录结构:
目录功能解释:
cmd
程序入口,目前有httpserver和管理员创建两个功能。config
配置文件存放的地方,config.yaml是全局配置文件,logmapping.json是日志文件的mapping配置。internal
是整个项目内容所在目录,go中internal目录用来对代码进行访问控制。app
是整个项目接口及数据库及路由编写的地方。apptest
是对app中的接口进行测试的地方。config
是config struct 定义的地方。middler
是中间件存放的地方。pkg
是工具代码存放的地方。router
基础路及其路由中间件代码。
log
日志输出位置。script
一些自动化脚本存放地方。swagdoc
swagger json文件存放的地方。- main.go 整个项目入口文件。
- Taskfile.yml 自动化测试,文档生成的任务编排配置文件。
整个目录结构中规中矩,比较容易理解。
整个项目有比较完整的测试,文档生成方案。为了自动化完成这些操作,使用了task一个go写的自动化任务软件。那个Taskfile.yml正是这个软件的配置文件。
重构main.go入口文件
入口文件是个启动文件,最主要功能是获取参数,包括命令行参数,配置文件参数,只有获取了这些参数。整个程序才能继续进行,代码如下。
var configFile = flag.String("f", "./config/config.yaml", "the config file")
func main() {
flag.Usage = Usage
flag.Parse()
var c config.Config
yamconfig.MustLoad(*configFile, &c)
if len(os.Args) < 2 {
flag.Usage()
os.Exit(1)
}
cmder := os.Args[1]
switch cmder {
case "run":
cmd.Server(c)
case "super":
super := cmd.NewSuperman(c)
if err := super.CreateSuperman(); err != nil {
log.Println(err)
}
default:
flag.Usage()
os.Exit(1)
}
}
func Usage() {
fmt.Println("go server v1.0")
fmt.Println("main [cmd] [tag]")
fmt.Println("cmd:")
fmt.Println(" run: server run")
fmt.Println(" super: create or update super account")
fmt.Println("tag:")
flag.PrintDefaults()
}
运行命令 go run main.go run
即可把服务启动,运行go run main.go super
即可创建管理员或者重置超级管理员密码。
从代码中可以看出,首先初始化了配置文件对象var c config.Config
,这个配置文件对象带入到cmd.Server中。
服务启动代码 cmd/server.go
cmd/server.go 是整个httpserver的启动逻辑代码。代码如下:
package cmd
import (
"myadmin/internal/app"
"myadmin/internal/config"
"myadmin/internal/middler"
"myadmin/internal/serctx"
"github.com/gin-gonic/gin"
"gs/api/apiserver"
"gs/pkg/metric"
)
func Server(c config.Config) {
//资源初始化
sc, err := serctx.NewServerContext(c)
if err != nil {
panic(err)
}
//服务 中间件
engine := gin.New()
apiserver.RegMiddler(engine,
apiserver.WithMiddle(middler.RegMiddler(sc)...),
apiserver.WithStatic("/view", c.Api.ViewDir),
)
//启动promagent
metric.StartAgent(engine, "/metrics", sc.Config.Prom.UserName, sc.Config.Prom.Password)
//注册路由
app.RegisterRoute(engine, sc)
//注册数据库
app.Regdb(sc)
//启动
apiserver.Run(engine, sc.Log.Logger, c.Api)
}
重点看这段代码
//资源初始化
sc, err := serctx.NewServerContext(c)
if err != nil {
panic(err)
}
这部分在第一篇文章中项目启动也介绍过。主要是启动或者初始化相关资源,此项目就是启动数据库连接,启动redis,配置数据库对象。
serctx/serctx.go
代码如下:
package serctx
import (
"myadmin/internal/config"
"gs/pkg/logx"
"gs/pkg/mysqlx"
"gs/pkg/redisx"
"github.com/go-redis/redis/v8"
errs "github.com/pkg/errors"
"gorm.io/gorm"
)
//所有资源放在此处
type ServerContext struct {
Config config.Config
Log *logx.Logx
Db *gorm.DB
Redis *redis.Client
}
func NewServerContext(c config.Config) (*ServerContext, error) {
//初始化日志
sc := &ServerContext{}
sc.Config = c
if lg, err := logx.NewLogx(c.Log); err != nil {
return nil, err
} else {
sc.Log = lg
}
//初始化数据库
db := mysqlx.NewDb(c.Mysql)
if d, err := db.GetDb(); err != nil {
return nil, errs.WithMessage(err, "err init db")
} else {
d.Debug()
sc.Db = d
sc.Log.Info("数据库初始化完成")
}
//初始化redis
if redisCli, err := redisx.NewRedis(c.Redis).GetDb(); err != nil {
return nil, errs.WithMessage(err, "err init redis")
} else {
sc.Redis = redisCli
sc.Log.Info("redis初始化完成")
}
return sc, nil
}
这个serverContext很重要,后期需要添加任何外部模块,都可以在此处进行初始化,并添加到ServerContext对象中。这个对象会通过路由注入到所有的handler中。使用起来,修改起来都很方便。
路由
整套路由设计把返回数据进行统计格式化处理。各个模块路由由模块处定义。
在上边server.go代码中,有一行如有注册函数。
app.RegisterRoute(engine, sc)
这个app就是那个app目,所有api编写的地方,看一下app下边的目录结构,目前只有一个admin模块,因为该项目只是写一个admin管理平台:
regrouter.go 是各个app注册的总路由注册地方。 代码如下:
package app
import (
"github.com/gin-gonic/gin"
"myadmin/internal/app/admin"
"myadmin/internal/router"
"myadmin/internal/serctx"
)
var routes = []func(r *router.Router, sc *serctx.ServerContext){
//所有路由按照app注册在此
admin.Route,
}
func RegisterRoute(engine *gin.Engine, sc *serctx.ServerContext) {
r := router.NewRouter(engine, sc)
for _, v := range routes {
v(r, sc)
}
}
再看一下admin的router.go路由:
所有admin的路由及handler整齐写在一块。
这就是整个路由链路。
还有两个很重的地方没有解释。
基础路由就是那个router目录下的东西。
这部分主要两个作用,一个是定义基础路由的中间件,一个是统计管理handler的异常返回。
基础如有中间件管理在router/router.go里,代码如下:
/**
路由基础定义
*/
type Router struct {
Root *gin.RouterGroup
Jwt *gin.RouterGroup //jwt登陆
}
func NewRouter(g *gin.Engine, sc *serctx.ServerContext) *Router {
return &Router{
Root: g.Group("/"),
Jwt: g.Group("/api", middler.TokenPase(sc), middler.LoginCode(sc)),
}
}
从代码中可以看出,凡是Jwt开头的都有 "/api"前缀,并且带上了token解析和,唯一登录的中间件(一台机子登陆后,另一台同个账号登陆,则第一台机子账号下线。),而root路由则没有任何限制。
异常返回统一处理在 router/do.go 中代码如下:
从代码中可以看出,handler如果返回错误,如果类型不是hd.ErrResponse则会将异常字符串化直接返回,其实非多语言中,handler直接 return errors.New("xxxxx")
就能把想要的异常信息返还给前端。
整个路由介绍完了,下面介绍一下handler的编写。
admin的handler很多,除了账号的增删查个,还有个人的查改登陆登出,token管理等。
拿一个账号检索(get.go)的代码看看:
把用到的资源都写在struct中 ,handler中所有用到的东西都直接拿来用。
结尾
这篇文章主要讲解了目录结构和路由。 下次讲讲中间件。
转载自:https://juejin.cn/post/7171105576695382030