简介
基于 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 文件。