项目源码
前言
我们在上篇对项目做了初始化,并启动了一个简单的HTTP
服务,在文末也提到了实际中的项目更为复杂,也更加庞大,需要有一个好的规划去实现,这样才能提高扩展性和可维护性
所以我们在这里先规划一下大概的一个框架:
➜ eve_api git:(main) ✗ tree -d
.
├── app
│ ├── api ---- 接口
│ │ ├── admin
│ │ └── app
│ ├── event
│ │ ├── event ---- 事件实体
│ │ └── listener ---- 事件监听器
│ ├── middleware ---- 中间件
│ ├── model ---- 数据库模型
│ ├── request ---- 请求实体
│ ├── response ---- 返回实体
│ ├── route ---- 路由
│ ├── service ---- service
│ └── task ---- 定时任务
├── cmd ---- 命令
├── internal ---- 外部库封装
│ ├── bootstrap ---- 启动初始化
│ ├── config ---- 配置文件
│ ├── event ---- 时间模块
│ ├── global ---- 全局变量
│ ├── logger ---- 日志
│ ├── mysql ---- 数据库
│ ├── redis ---- redis
│ ├── server ---- http服务
│ │ ├── middleware
│ │ └── route
│ ├── tool ---- 工具
│ └── validator ---- 验证器
├── test ---- 单元测试
└── tmp ---- 临时文件目录
└── log ---- 日志文件
如上所示,我们先对项目做了结构上的规划,然后再陆续填充内容
本篇先对上篇实现的简单HTTP服务进行重构
HTTP服务重构
上篇中我们实现的HTTP服务如下:
r := gin.New()
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"Message": "Hello World"})
})
err := r.Run(":8082")
if err != nil {
return
}
可以看到是分三步
- 实例化一个对象
- 注册路由
- 启动
那我们也可以封装一个http包,包中包含http对象以及实现http对象的两个方法:GenRouter和Run
新建internal/server/http.go
文件:
package server
import (
"context"
"eve/internal/server/route"
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
type Http struct {
engine *gin.Engine // gin框架的核心组件
port string // http服务端口号
}
// New 实例化http对象
func New() *Http {
entity := &Http{
engine: gin.New(),
port: ":8082",
}
return entity
}
// GenRouter 注册路由
// 这里需要调用外部的route库,所以先定义一个Interface,并在外部实现Interface
func (h *Http) GenRouter(r route.RouterGeneratorInterface) {
r.AddRoute(h.engine)
}
// Run 启动服务
func (h *Http) Run() {
srv := &http.Server{
Addr: h.port,
Handler: h.engine,
}
go func() {
err := srv.ListenAndServe()
if err != nil {
fmt.Println(err)
}
}()
h.ListenSignal(srv)
}
func (h *Http) ListenSignal(srv *http.Server) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutdown server")
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("server shutdown ")
}
}
新增internal/server/route/route.go
文件:
package route
import "github.com/gin-gonic/gin"
type RouterGeneratorInterface interface {
AddRoute(server *gin.Engine)
}
新增app/route/route.go
文件:
package route
import (
"github.com/gin-gonic/gin"
)
type AppRouter struct{}
func (*AppRouter) AddRoute(e *gin.Engine) {
e.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "ping"})
})
e.GET("/pong", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
}
func New() *AppRouter {
return &AppRouter{}
}
然后再修改我们的启动文件cmd/start.go
:
// Package cmd /*
package cmd
import (
"eve/app/route"
"eve/internal/server"
"github.com/spf13/cobra"
)
// startCmd represents the start command
var startCmd = &cobra.Command{
Use: "start",
Short: "start serve",
Long: `start serve`,
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
func init() {
rootCmd.AddCommand(startCmd)
}
func run() {
http := server.New()
http.GenRouter(route.New())
http.Run()
}
启动服务:
➜ eve_api git:(main) ✗ go run main.go start
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> eve/app/route.(*AppRouter).AddRoute.func1 (1 handlers)
[GIN-debug] GET /pong --> eve/app/route.(*AppRouter).AddRoute.func2 (1 handlers)
可以看到重写的http服务已经生效
commit-hash: e8400fc
路由分组
再看app/route/route.go
文件AddRoute
方法:
func (*AppRouter) AddRoute(e *gin.Engine) {
e.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "ping"})
})
e.GET("/pong", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
}
它是在AddRoute
方法中直接注册路由的,单模块的话是没有问题的,但是正常情况下我们可能需要对客户端、管理后台等提供接口,多模块的情况下这个写法就显得杂乱,这就需要用到Gin框架的路由分组功能:
srv := gin.New()
appGroup := srv.Group("/app")
{
appGroup.GET("/a", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "a"})
})
appGroup.GET("/b", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "b"})
})
}
adminGroup := srv.Group("/admin")
{
adminGroup.POST("/login", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "login"})
})
}
可以看到我们创建了两个分组,分别对应/app
和/admin
,然后在各个分组内再注册各自的接口,看起来就更清晰,也方便后期添加不同的中间件
那么我们重新修改app/route/route.go
文件:
package route
import (
"github.com/gin-gonic/gin"
)
type AppRouter struct{}
func (*AppRouter) AddRoute(e *gin.Engine) {
genAdminRouter(e.Group("/admin"))
genAppRouter(e.Group("/app"))
}
func New() *AppRouter {
return &AppRouter{}
}
新增app/route/admin.go
文件:
package route
import (
"github.com/gin-gonic/gin"
)
func genAdminRouter(rg *gin.RouterGroup) {
rg.GET("/a", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "a"})
})
}
新增app/route/app.go
文件:
package route
import "github.com/gin-gonic/gin"
func genAppRouter(rg *gin.RouterGroup) {
rg.GET("/b", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "b"})
})
}
可以看到我们把路由注册放到了app
和admin
两个文件中,这样看起来就更清晰,后边维护起来也更加方便
总结
- 对项目整体结构做了规划
- 重构了http服务
- 实现了路由分组
commit-hash: 2b17fb7