golang之Gin项目脚手架搭建

Go编程 专栏收录该内容
16 篇文章 0 订阅

GoWeb之Gin项目脚手架搭建

一、Gin框架简单使用

Gin项目地址:https://github.com/gin-gonic/gin

文档地址:https://www.kancloud.cn/shuangdeyu/gin_book/949411(看云手册)和https://gin-gonic.com/zh-cn/docs/(官方文档)

1.1. Gin项目简介

Gin 是一个 go 写的 web 框架,具有高性能的优点。Go世界里面最流行的Web框架,基于httprouter开发的Web框架。 中文文档齐全,简单易用的轻量级框架。

1.2. 简单示例

package main

import "github.com/gin-gonic/gin"

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "pong",
    })
  })
  r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

是不是很简单,但是如何我们写一个项目的API接口服务,不可能将所有的接口和配置信息中间件都写到一个文件里面,这时候我们就需要考虑下项目结构。好的项目结构可以方便我们扩展和使用。

二、项目目录规范

.
├── api                                             存放接口的目录
│   └── home.go
├── cache                                           缓冲相关的目录
│   ├── cache.go
│   └── redis_cache.go
├── config                                          项目配置地址
│   ├── config.go
│   └── config.toml
├── global                                          全局变量redis、Mongodb连接
│   └── global.go
├── go.mod
├── go.sum
├── Dockerfile                                      镜像构建文件
├── logs                                            存放日志目录部署需要配置为其他目录
│   ├── system.log -> system.log.20210606.log
│   ├── system.log.20210605.log
│   └── system.log.20210606.log
├── middleware                                      中间件目录
│   ├── core_middle.go
│   └── log_middle.go
├── models                                          实体映射
│   └── home.go
├── repository                                      实体针对数据操作
│   └── home_repository.go
├── router.go                                       路由配置
├── server.go                                       启动配置
├── server_other.go                                 非win系统启动配置
├── server_win.go                                   win系统启动配置
├── service                                         业务操作
│   └── home_serivce.go
├── utils                                           工具目录,消息,工具方法
│   ├── message
│   └── tools
│       └── type_utils.go
└── view                                            返回或者接受的实体
    └── home_view.go

项目的包结构整理不好,很容易出现循环引用的问题,对于golang的新手很不友好啊!看了很多开源项目目录结构很多是把所有的go文件都放到根目录中,看的脑壳疼。

三、Gin启动和路由

3.1. 服务启动

这里我们考虑golang项目开发的时候很多是在win开发,但是在linux系统部署,考虑以后需要平滑重启(平滑重启就是升级server不停止业务),我们先引入一个组件:endless。

go get -u github.com/fvbock/endless

win服务启动配置:

// +build windows  

package main

import (
  "github.com/gin-gonic/gin"
  "net/http"
  "time"
)

func InitServer(address string, router *gin.Engine) Server {
  gin.SetMode(gin.ReleaseMode)
  return &http.Server{
    Addr:           address,
    Handler:        router,
    ReadTimeout:    10 * time.Second,
    WriteTimeout:   10 * time.Second,
    MaxHeaderBytes: 1 << 20,
  }
}

非win服务启动:

// +build !windows

package main

import (
  "github.com/fvbock/endless"
  "github.com/gin-gonic/gin"
  "time"
)

func InitServer(address string, router *gin.Engine) Server {
  gin.SetMode(gin.ReleaseMode)
  s := endless.NewServer(address, router)
  s.ReadHeaderTimeout = 10 * time.Millisecond
  s.WriteTimeout = 10 * time.Second
  s.MaxHeaderBytes = 1 << 20
  return s
}

这里我们使用的是条件编译,// +build !windows需要放在文件最顶部,并且空一行,否则不生效。这样就可以做到不同系统使用不同go文件编译了。

最后我们将这个启动的调用方法main方法中:

package main

import (
  "context"
  "github.com/gin-gonic/gin"
  "go.mongodb.org/mongo-driver/mongo"
  "go_init/global"
)

type Server interface {
  ListenAndServe() error
}

func RunServer() {
  // 关闭连接
  defer func(Mongo *mongo.Client, ctx context.Context) {
    err := Mongo.Disconnect(ctx)
    if err != nil {
      panic("关闭连接失败")
    }
  }(global.Mongo, context.TODO())
  // 初始化路由
  router := InitRouter(gin.Default())
  InitServer(global.Config.Server.Part, router).ListenAndServe().Error()
}

func main() {
  RunServer()
}

3.2. 路由配置

接着配置项目的路由部分,在上面我们通过InitRouter(gin.Default())初始化路由。这里我们在启动路由的时候配置相关的中间件,并将接口的handler和对应的接口路由一一对应。

import (
  "github.com/gin-gonic/gin"
  "go_init/api"
  "go_init/middleware"
)

func InitRouter(engine *gin.Engine) *gin.Engine {
  engine.Use(middleware.CorsMiddle())
  engine.Use(middleware.LogMiddle())
  group := engine.Group("api")
  {
    HomeRouter(group)
  }
  return engine
}



func HomeRouter(g *gin.RouterGroup) {
  homeController := api.NewHomeController()
  home := g.Group("home")
  {
    home.POST("add", homeController.HomeAdd)
    home.GET("list", homeController.HomeIndex)
  }
}

3.3. 跨域中间件

现在项目基本上都是前后端分离的项目,跨域是必不可少的。

import (
  "github.com/gin-gonic/gin"
  "net/http"
)

func CorsMiddle() gin.HandlerFunc {
  return func(c *gin.Context) {
    method := c.Request.Method
    origin := c.Request.Header.Get("Origin")
    c.Header("Access-Control-Allow-Origin", origin)
    c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id")
    c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
    c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
    c.Header("Access-Control-Allow-Credentials", "true")

    // 放行所有OPTIONS方法
    if method == "OPTIONS" {
      c.AbortWithStatus(http.StatusNoContent)
    }
    // 处理请求
    c.Next()
  }
}

关于日志的中间件,我们后面在介绍

四、整合数据库之MongoDB

这里数据我选择了MongoDB,也可以选择MySQL,PG等等。

go get -u go.mongodb.org/mongo-driver

这我们添加这个依赖还需要,go mod tidy 一下,将缺失的依赖都补全。

这里我先添加全局的global文件,存放我们服务运行的时候需要一直保持的连接配置等信息

var (
  Config config.Config     // 配置类
  Mongo  *mongo.Client     // mongodb的连接
  DB     *mongo.Database   // mongodb指定库的连接
  Redis  *cache.RedisPool  // redis的连接池
)

看一下Mongodb的初始化方法:

func InitMongoDB() (*mongo.Client, *mongo.Database) {
  m := Config.Mongodb
  clientOptions := options.Client().ApplyURI(m.Link())
  client, err := mongo.Connect(context.TODO(), clientOptions)
  if err != nil {
    panic("连接MongoDB失败")
  }
  err = client.Ping(context.TODO(), nil)
  if err != nil {
    panic("Ping => 测试连接失败")
  }
  return client, client.Database(m.Database)
}

最后在global文件中增加一个init方法。用于调用初始化Mongodb的方法。

func init() {
  Config = config.InitConfig()
  Mongo, DB = InitMongoDB()
  Redis = InitRedisPool()
}

五、整合日志之Logrus

golang的日志框架很多,选一个用的顺手的就好,就选Logrus就好,其他的整合大同小异。

先添加依赖包:

go get -u github.com/sirupsen/logrus
go get -u github.com/lestrrat-go/file-rotatelogs 
go get -u github.com/rifflock/lfshook 

这里涉及日志的格式格式输出、日志写入文件和日志分割等。

import (
  "fmt"
  "github.com/gin-gonic/gin"
  rotatelogs "github.com/lestrrat-go/file-rotatelogs"
  "github.com/rifflock/lfshook"
  "github.com/sirupsen/logrus"
  "go_init/global"
  "os"
  "path"
  "time"
)

func LogMiddle() gin.HandlerFunc {
  logCfg := global.Config.Logger
  // 日志文件
  fileName := path.Join(logCfg.FilePath, logCfg.FileName)
  // 写入文件
  src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
  if err != nil {
    fmt.Println("Error", err)
  }
  // 实例化
  logger := logrus.New()
  //设置日志级别
  logger.SetLevel(logrus.DebugLevel)
  //设置输出
  logger.Out = src
  // 设置 rotatelogs
  logWriter, err := rotatelogs.New(
    // 分割后的文件名称
    fileName+".%Y%m%d.log",
    // 生成软链,指向最新日志文件
    rotatelogs.WithLinkName(fileName),
    // 设置最大保存时间(7天)
    rotatelogs.WithMaxAge(7*24*time.Hour),
    // 设置日志切割时间间隔(1天)
    rotatelogs.WithRotationTime(24*time.Hour),
  )

  writeMap := lfshook.WriterMap{
    logrus.InfoLevel:  logWriter,
    logrus.FatalLevel: logWriter,
    logrus.DebugLevel: logWriter,
    logrus.WarnLevel:  logWriter,
    logrus.ErrorLevel: logWriter,
    logrus.PanicLevel: logWriter,
  }

  logger.AddHook(lfshook.NewHook(writeMap, &logrus.JSONFormatter{
    TimestampFormat: "2006-01-02 15:04:05",
  }))


  return func(c *gin.Context) {
    //开始时间
    startTime := time.Now()
    //处理请求
    c.Next()
    //结束时间
    endTime := time.Now()
    // 执行时间
    latencyTime := endTime.Sub(startTime)
    //请求方式
    reqMethod := c.Request.Method
    //请求路由
    reqUrl := c.Request.RequestURI
    //状态码
    statusCode := c.Writer.Status()
    //请求ip
    clientIP := c.ClientIP()

    // 日志格式
    logger.WithFields(logrus.Fields{
      "status_code":  statusCode,
      "latency_time": latencyTime,
      "client_ip":    clientIP,
      "req_method":   reqMethod,
      "req_uri":      reqUrl,
    }).Info()
  }
}

六、整合缓冲之Redis

我们这里整合redis需要对redis的一些方法做一下简单封装

go get -u github.com/gomodule/redigo

初始化的redis连接池也放在global文件中:

func InitRedisPool() *cache.RedisPool {
  r := Config.Redis
  return &cache.RedisPool{
    Pool: redis.Pool{
      Dial: func() (redis.Conn, error) {
        return redis.Dial(r.Dial,
          fmt.Sprintf("%s:%s", r.Ip, r.Part),
          redis.DialPassword(r.Password),
          redis.DialDatabase(r.Database))
      },
      MaxIdle: r.MaxIdle,
      MaxActive: r.MaxActive,
    },
  }
}

下面是对于redis简单的封装

import (
  "encoding/json"
  "fmt"
  "github.com/gomodule/redigo/redis"
)

var (
  SET = "SET"
  EXISTS = "EXISTS"
  GET = "GET"
  EXPIRE = "EXPIRE"
  DEL = "DEL"
  KEYS = "KEYS"
)

type RedisPool struct {
  Pool redis.Pool
}

// Conn 获取数据连接
func (p *RedisPool) conn() redis.Conn {
  return p.Pool.Get()
}

// Set 设置数据
func (p *RedisPool) Set(key string, value interface{}, time int) (bool, error) {
  conn := p.conn()
  data, err := json.Marshal(value)
  if err != nil {
    return false, err
  }
  if _, err = conn.Do(SET, key, data); err != nil {
    return false, err
  }
  _, _ = conn.Do(EXPIRE, key, time)
  return true, nil
}

// Exists 是否存在
func (p *RedisPool) Exists(key string) bool {
  conn := p.conn()
  flag, err := redis.Bool(conn.Do(EXISTS, key))
  if err != nil {
    return false
  }
  return flag
}

// Get 获取数据
func (p *RedisPool) Get(key string) ([]byte, error) {
  conn := p.conn()
  data, err := redis.Bytes(conn.Do(GET, key))
  if err != nil {
    return nil, err
  }
  return data, err
}

// Delete 删除
func (p *RedisPool) Delete(key string) (bool, error) {
  conn := p.conn()
  return redis.Bool(conn.Do(DEL, key))
}

// BlurryDel 模糊删除
func (p *RedisPool) BlurryDel(key string) error {
  conn := p.conn()
  keys, err := redis.Strings(conn.Do(KEYS, fmt.Sprintf("*%s*", key)))
  if err != nil {
    return err
  }
  for _, key := range keys {
    p.Delete(key)
  }
  return nil
}

简单使用:选判断缓冲是否存在,存在就从缓冲中拿到数据后返回,不存在就去查询,查询之后在放入缓冲中。

// Select 获取所有数据
func (h *HomeService) Select() ([]models.Home, error) {
  data, err := global.Redis.Get(CacheStudent)
  if err != nil {
    all, err := h.HomeRepository.FindAll()
    if err != nil {
      return nil, err
    }
    set, err := global.Redis.Set(CacheStudent, all, 1000000)
    if err != nil {
      logrus.Infof("发生错误:%s, %v", err.Error(), set)
    }
    return all, nil
  }
  var result []models.Home
  err = json.Unmarshal(data, &result)
  return result, err
}

七、镜像构建

golang项目构建docker镜像也很好用,下面我贴一下构建文件。可以参考一下。

FROM golang:1.16.3 as builder

# 设置容器环境变量
ENV GOPROXY=https://goproxy.cn
ENV GOOS=linux
ENV GOARCH=amd64
ENV CGO_ENABLED=0

COPY . /app

WORKDIR /app

RUN go get -u github.com/fvbock/endless

RUN go build -ldflags="-s -w" -installsuffix cgo -o go_init

FROM alpine as prod

# 开放端口
EXPOSE 9091

# 创建一个目录
RUN mkdir -p /app/logs

RUN chmod 666 /app/logs

RUN ls /app

COPY --from=builder /app/go_init /app

COPY --from=builder /app/config.toml /app

# 启动
CMD ["/app/go_init", "--config=/app/config.toml"]

七、项目地址

对你有帮助,记得点个star,感谢!

https://gitee.com/molonglove/gin-init-scaffold.git

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值