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

106 阅读3分钟

简介

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

  • conf:配置读取;
  • controllers:基础 api 方法封装,应用 api;
  • iorm:beego 的持久化模块扩展,提供了实体泛型,封装了通用的 crud 方法,事务方法,增加了软删除机制;
  • logger:日志模块封装,日志文件分割配置;
  • models:基础实体定义;
  • routers:api 路由注册,路由常用配置;
  • main:应用启动入口。

模块

controllers

基于 web.Controller 扩展,提供通用方法如入参 body 校验,请求逻辑终止,错误码处理,统一响应结构。

controller 示例:

type ExampleController struct {
    BaseController
}

func (ctrl ExampleController) Success() {
    ctrl.writeSuccess("success")
}

func (ctrl ExampleController) Authentication() {
    ctrl.abort401()
}

func (ctrl ExampleController) InternalServerError() {
    ctrl.abort("internal server error")
}

func (ctrl ExampleController) Body() {
    type param struct {
       String string `json:"string" valid:"Required"`
       Int    int    `json:"int" valid:"Range(1, 10)"`
       Bool   bool   `json:"bool"`
       Name   string `json:"name" valid:"MaxSize(5)"`
    }

    body := param{}
    err := ctrl.BindJSON(&body)
    if err != nil {
       ctrl.abort(err.Error())

    }
    ok, errors := ctrl.validWithoutAbort(&body)
    if !ok {
       fmt.Println(errors)
    }
    ctrl.valid(&body)
    ctrl.writeSuccess(body)
}

错误码处理:

// 注册 controller 错误自定义处理
web.ErrorController(&controllers.ErrorController{})

const errorHandlerMsgKey = "error"

type ErrorController struct {
    web.Controller
}

func (ctrl *ErrorController) Error400() {
    ctrl.error(400, "bad request")
}

func (ctrl *ErrorController) Error401() {
    ctrl.error(401, "unauthorized")
}

func (ctrl *ErrorController) Error403() {
    ctrl.error(403, "forbidden")
}

func (ctrl *ErrorController) Error404() {
    ctrl.error(404, "page not found")
}

func (ctrl *ErrorController) Error500() {
    ctrl.error(500, "internal server error")
}

func (ctrl *ErrorController) error(code int, err string) {
    if errorString, ok := ctrl.Data[errorHandlerMsgKey].(string); ok {
       err = errorString
    }
    _ = ctrl.Ctx.JSONResp(models.ErrorResponseWithCode(code, err))
}

iorm

beego 的持久化模块扩展,提供了基础实体类型定义,封装了通用的 crud 方法,事务方法,增加 deleted_at 字段实现了软删除操作。

基础实体类型定义:

type IModel interface {
    TableName() string
    GetId() int64
    SetId(id int64)
    SetDeleteAt(time time.Time)
    BeforeInsert()
}

type Model struct {
    Id        int64     `orm:"pk;column(id);description(primary key)"`
    CreatedAt time.Time `orm:"auto_now_add;type(datetime)"`
    UpdatedAt time.Time `orm:"auto_now;type(datetime)"`
    DeletedAt time.Time `orm:"null"`
    CreatedBy string    `orm:"size(50);null"`
    Creator   string    `orm:"size(50);null"`
    UpdateBy  string    `orm:"size(50);null"`
    Updater   string    `orm:"size(50);null"`
}

func (m *Model) GetId() int64 {
    return m.Id
}

func (m *Model) SetId(id int64) {
    m.Id = id
}

func (m *Model) SetDeleteAt(time time.Time) {
    m.DeletedAt = time
}

func (m *Model) BeforeInsert() {
    m.Id = int64(utils.GenSnowFlakeId())
    if m.CreatedBy == "" {
       m.CreatedBy = "system"
    }
    if m.Creator == "" {
       m.Creator = "system"
    }
    if m.UpdateBy == "" {
       m.UpdateBy = "system"
    }
    if m.Updater == "" {
       m.Updater = "system"
    }
}

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

代码示例:

type iModel = models.IModel

var unScopedCondition *orm.Condition

func init() {
    unScopedCondition = orm.NewCondition().And("deleted_at__isnull", true)
}

type ormConfig struct {
    desc      bool
    condition *orm.Condition
    unScoped  bool
}

type QueryOption func(config *ormConfig)

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

func WithCondition(condition *orm.Condition) QueryOption {
    return func(config *ormConfig) {
       config.condition = condition
    }
}

type IOrm[M any] struct {
    unScoped bool
}

func (iorm *IOrm[M]) querySeterWithConfig(qs orm.QuerySeter, config *ormConfig) orm.QuerySeter {
    if config == nil {
       return qs
    }
    if config.desc {
       qs = qs.OrderBy("-created_at")
    }
    if config.condition != nil {
       qs = qs.SetCond(config.condition)
    }
    if !config.unScoped {
       if qs.GetCond() == nil {
          qs = qs.SetCond(unScopedCondition)
       } else {
          qs = qs.SetCond(qs.GetCond().AndCond(unScopedCondition))
       }
    }
    return qs
}

func (iorm *IOrm[M]) Find(m iModel, options ...QueryOption) ([]*M, error) {
    var mds []*M
    qs := orm.NewOrm().QueryTable(m.TableName())
    config := iorm.handleOptions(options...)
    qs = iorm.querySeterWithConfig(qs, config)
    _, err := qs.All(&mds)
    return mds, err
}

func (iorm *IOrm[M]) Create(m iModel) error {
    o := orm.NewOrm()
    return iorm.TCreate(o, m)
}

func (iorm *IOrm[M]) TCreate(tx orm.QueryExecutor, m iModel) error {
    m.BeforeInsert()
    // Insert 方法返回主键自增的值,需要设置主键自增
    _, err := tx.Insert(m)
    return err
}

func (iorm *IOrm[M]) UnScoped() *IOrm[M] {
    return &IOrm[M]{unScoped: true}
}

func (iorm *IOrm[M]) Transaction(fc func(ctx context.Context, tx orm.TxOrmer) error) {
    _ = orm.NewOrm().DoTx(fc)
}

定义示例:

type Example struct {
    models.Model
    String string `orm:"null"`
    Text   string `orm:"type(text);null"`
    Bool   bool
    Parent int64
}

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

type ExampleOrm struct {
    IOrm[Example]
}

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

查询调用示例:

func TestFind(t *testing.T) {
    condition := orm.NewCondition().And("id__in", []int64{1})
    
    // 查询
    exampleOrm.Find(&Example{})
    // 倒序条件
    exampleOrm.Find(&Example{}, Desc())
    // 添加自定义条件
    exampleOrm.Find(&Example{}, Desc(), WithCondition(condition))
    // 真实查询
    exampleOrm.UnScoped().Find(&Example{}, Desc(), WithCondition(condition))
    
    // 查询
    exampleOrm.FindWithPaging(&Example{}, 1, 10)
    // 倒序条件
    exampleOrm.FindWithPaging(&Example{}, 1, 10, Desc())
    // 添加自定义条件
    exampleOrm.FindWithPaging(&Example{}, 1, 10, Desc(), WithCondition(condition))
    // 真实查询
    exampleOrm.UnScoped().FindWithPaging(&Example{}, 1, 10, Desc(), WithCondition(condition))
}
创建调用示例:
func TestCreate(t *testing.T) {
    // 添加
    exampleOrm.Create(example)
    // 事务
    exampleOrm.Transaction(func(tx *gorm.DB) error {
        exampleOrm.TCreate(tx, example)
        return errors.New("transaction err")
    })
}

最后

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