Golang 学习笔记 - 基于 gin 的应用脚手架

简介

基于 gin 封装的脚手架,纯后端项目,封装了一些常用的项目基本功能,项目结构如下:

  • config:配置读取;
  • controllers:应用 api;
  • engine:gin 的启停;
  • iorm:基于 gorm 框架的持久化模块扩展,提供了泛型配置,封装了通用的 crud 方法,事务方法,软删除切换;
  • logger:基于 zap 的日志模块封装,日志文件分割配置;
  • models:基础实体定义;
  • routers:api 路由注册,路由常用配置;
  • main:应用启动入口。

模块

controllers

提供 HandlerRegisterFunc,handler 注册函数,错误码处理,统一响应结构,cors 封装。

代码示例:


engine.Use(responseHandler())
engine.NoRoute(notFound())

func notFound() gin.HandlerFunc {
    return func(ctx *gin.Context) {
       ctx.JSON(http.StatusNotFound, models.ErrorResponseWithCode(http.StatusNotFound, "page not found"))
    }
}

func responseHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
       c.Next()

       if len(c.Errors) > 0 {
          for _, e := range c.Errors {
             switch e.Type {
             case gin.ErrorTypeBind:
                var errs validator.ValidationErrors
                ok := errors.As(e.Err, &errs)

                if !ok {
                   writeError(c, e.Error())
                   return
                }

                var stringErrors []string
                for _, err := range errs {
                   stringErrors = append(stringErrors, translate(err))
                }
                writeError(c, strings.Join(stringErrors, "; "))
             default:
                writeError(c, e.Err.Error())
             }
          }
       }
    }
}

func translate(e validator.FieldError) string {
    field := e.Field()
    switch e.Tag() {
    case "required":
       return fmt.Sprintf("Field '%s' is required", field)
    case "max":
       return fmt.Sprintf("Field '%s' must be less or equal to %s", field, e.Param())
    case "min":
       return fmt.Sprintf("Field '%s' must be more or equal to %s", field, e.Param())
    }
    return e.Error()
}

func writeError(ctx *gin.Context, errString string) {
    status := ctx.Writer.Status()
    ctx.JSON(status, models.ErrorResponseWithCode(status, errString))
}

iorm

gorm 的持久化模块扩展,提供了基础实体类型定义,封装了通用的 crud 方法,事务方法。

基础实体类型定义:

type Model struct {
    ID        int64 `gorm:"primarykey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    CreatedBy string         `gorm:"size:50"`
    Creator   string         `gorm:"size:50"`
    UpdateBy  string         `gorm:"size:50"`
    Updater   string         `gorm:"size:50"`
}

func (base *Model) BeforeCreate(tx *gorm.DB) (err error) {
    base.ID = int64(utils.GenSnowFlakeId())
    if base.CreatedBy == "" {
       base.CreatedBy = "system"
    }
    if base.Creator == "" {
       base.Creator = "system"
    }
    if base.UpdateBy == "" {
       base.UpdateBy = "system"
    }
    if base.Updater == "" {
       base.Updater = "system"
    }
    return
}

igorm,使用 Transaction 方法开启事务,并使用 T 开头的事务方法;默认查询方法只会查询 deleted_at 字段不为空的记录,删除则为软删除,调用 UnScoped() 方法则代表进入真实查询与真实删除模式。

代码示例:

type GMap = map[string]interface{}

var gdb *gorm.DB

type ormConfig struct {
    desc          bool
    cols          []string
    conditionFunc func(tx *gorm.DB)
    unScoped      bool
}

type QueryOption func(config *ormConfig)

func Desc() QueryOption {
    return func(config *ormConfig) {
       config.desc = true
    }
}

func SelectCols(cols ...string) QueryOption {
    return func(config *ormConfig) {
       config.cols = cols
    }
}

func WithCondition(conditionFunc func(tx *gorm.DB)) QueryOption {
    return func(config *ormConfig) {
       config.conditionFunc = conditionFunc
    }
}

type IGorm[T any] struct {
    unScoped bool
}

func (iGorm *IGorm[T]) getDB(tx ...*gorm.DB) *gorm.DB {
    db := gdb
    if len(tx) > 0 {
       db = tx[0]
    }
    if iGorm.unScoped {
       return db.Unscoped()
    } else {
       return db
    }
}

func (iGorm *IGorm[T]) Find(options ...QueryOption) ([]*T, error) {
    tx := iGorm.getDB().Model(new(T))
    config := iGorm.handleOptions(options...)
    iGorm.txWithConfig(tx, config)

    var mds []*T
    err := tx.Find(&mds).Error
    return mds, err
}

func (iGorm *IGorm[T]) Update(t *T, column string, value interface{}) error {
    return iGorm.getDB().Model(t).Update(column, value).Error
}

func (iGorm *IGorm[T]) DbInstance() *gorm.DB {
    tx := iGorm.getDB().Model(new(T))
    return tx
}

func (iGorm *IGorm[T]) TxWrapper(options ...QueryOption) *gorm.DB {
    tx := iGorm.getDB().Model(new(T))
    config := iGorm.handleOptions(options...)
    iGorm.txWithConfig(tx, config)
    return tx
}

func (iGorm *IGorm[T]) UnScoped() *IGorm[T] {
    return &IGorm[T]{unScoped: true}
}

func (iGorm *IGorm[T]) Transaction(fc func(tx *gorm.DB) error) {
    _ = iGorm.getDB().Transaction(fc)
}

定义示例:

type Example struct {
    models.Model
    String string
    Text   string `gorm:"type:TEXT"`
    Bool   bool
    Parent int64
}

func (Example) TableName() string {
    return "example"
}

type ExampleOrm struct {
    IGorm[Example]
}

var exampleOrm *ExampleOrm
var example = &Example{
    String: "string",
    Text:   "text",
    Bool:   true,
}

查询调用示例:

func TestFind(t *testing.T) {
    conditionFunc := func(tx *gorm.DB) {
       tx.Where("id in ?", []int64{1})
    }

    // 查询
    exampleOrm.Find()
    // 倒序条件
    exampleOrm.Find(Desc())
    // 添加自定义条件
    exampleOrm.Find(Desc(), WithCondition(conditionFunc))
    // 真实查询
    exampleOrm.UnScoped().Find(Desc(), WithCondition(conditionFunc))

    // 添加查询字段
    exampleOrm.Find(Desc(), WithCondition(conditionFunc), SelectCols("text"))
    exampleOrm.UnScoped().Find(Desc(), WithCondition(conditionFunc), SelectCols("text", "string"))
}

更新调用示例:

func TestUpdate(t *testing.T) {
    example.ID = 1
    // 更新单个字段
    exampleOrm.Update(example, "text", "new text")
    exampleOrm.Transaction(func(tx *gorm.DB) error {
       exampleOrm.TUpdate(tx, example, "text", "new text")
       return errors.New("transaction err")
    })

    // 根据 struct 字段更新
    example.Text = "new text"
    example.String = "new string"
    example.Bool = false
    exampleOrm.UpdateWithStruct(example)
    exampleOrm.Transaction(func(tx *gorm.DB) error {
       exampleOrm.TUpdateWithStruct(tx, example)
       return errors.New("transaction err")
    })

    // 根据 map 更新
    gMap := GMap{}
    gMap["text"] = "new text"
    gMap["string"] = "new string"
    gMap["bool"] = false
    exampleOrm.UpdateWithMap(example, gMap)
    exampleOrm.Transaction(func(tx *gorm.DB) error {
       exampleOrm.TUpdateWithMap(tx, example, gMap)
       return errors.New("transaction err")
    })
}

最后

完整代码已经上传仓库:igolang,更多示例请参考模块目录下的 test 文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值