1.错误处理

错误处理中往往最常见的情况是数据库交互,比如sql错误信息,用户表单输入错误等,后端敏感错误不应该返回给前端接口,所以需要对敏感错误拦截,返回前端友好错误码及错误信息。
可以通过 g.Try()对错误进行捕获和处理:

func (s *sysUser) AddUser(req *model.AddUserReq) (err error) {
    req.UserSalt = grand.S(10)
    req.Password = library.EncryptPassword(req.Password, req.UserSalt)
    var tx *gdb.TX
    tx, err = g.DB().Begin()
    if err != nil {
        err = gerror.New("事务开启失败")
        return
    }
    Model := dao.SysUser.TX(tx)
    if i, _ := Model.Where("user_name=?", req.UserName).Count(); i != 0 {
        err = gerror.New("用户名已经存在")
        tx.Rollback()
        return
    }
    if i, _ := Model.Where("mobile=?", req.Phonenumber).Count(); i != 0 {
        err = gerror.New("手机号已经存在")
        tx.Rollback()
        return
    }
    userData := new(model.SysUser)
    userData.UserName = req.UserName
    userData.DeptId = req.DeptId
    userData.UserStatus = req.Status
    userData.Mobile = req.Phonenumber
    userData.Sex = req.Sex
    userData.UserEmail = req.Email
    userData.UserNickname = req.NickName
    userData.UserSalt = req.UserSalt
    userData.UserPassword = req.Password
    userData.Remark = req.Remark
    userData.IsAdmin = req.IsAdmin
    res, err := Model.Insert(userData)
    if err != nil {
        tx.Rollback()
        return
    }
    InsertId, _ := res.LastInsertId()
    err = s.AddUserRole(req.RoleIds, InsertId)
    if err != nil {
        g.Log().Error(err)
        err = gerror.New("设置用户权限失败")
        tx.Rollback()
        return
    }
    err = s.AddUserPost(req.PostIds, InsertId, tx)
    if err != nil {
        g.Log().Error(err)
        err = gerror.New("设置用户岗位信息失败")
        tx.Rollback()
        return
    }
    tx.Commit()
    return
}

如上代码充斥着大量的 if err!=nil这样的代码,并且将sql报错直接当error 返回,可能暴露数据库相关敏感信息。
可以通过 g.Try来优化代码:

func (s *userImpl) Add(ctx context.Context, req *system.UserAddReq) (err error) {
    err = s.userNameOrMobileExists(ctx, req.UserName, req.Mobile)
    if err != nil {
        return
    }
    req.UserSalt = grand.S(10)
    req.Password = libUtils.EncryptPassword(req.Password, req.UserSalt)
    err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
        err = g.Try(func() {
            userId, e := dao.SysUser.Ctx(ctx).TX(tx).InsertAndGetId(do.SysUser{
                UserName:     req.UserName,
                Mobile:       req.Mobile,
                UserNickname: req.NickName,
                UserPassword: req.Password,
                UserSalt:     req.UserSalt,
                UserStatus:   req.Status,
                UserEmail:    req.Email,
                Sex:          req.Sex,
                DeptId:       req.DeptId,
                Remark:       req.Remark,
                IsAdmin:      req.IsAdmin,
            })
            liberr.ErrIsNil(ctx, e, "添加用户失败")
            e = s.addUserRole(ctx, req.RoleIds, userId)
            liberr.ErrIsNil(ctx, e, "设置用户权限失败")
            e = s.AddUserPost(ctx, tx, req.PostIds, userId)
            liberr.ErrIsNil(ctx, e)
        })
        return err
    })
    return
}

可以看到代码量明显减少,并可通过 liberr.ErrIsNil(ctx, err, "添加用户失败")方法将sql错误包装后返回明文错误。
一旦发生错误前端得到错误信息:

{
    "code": 50,
    "message": "添加用户失败",
    "data": null
}

而后端控制台则可查看对应错误堆栈信息:

2.接口化

接口化是更高层次的抽象。框架组件的设计尽可能使用了接口化,而不是尽可能提供具体实现。接口化设计的最大的好处,是允许使用者自定义实现,来替换组件底层的接口层,以实现很强的灵活性和扩展性。
gf文档参考:https://goframe.org/pages/viewpage.action?pageId=35356693

###

3.不要使用init方法隐式初始化

隐式初始化一般通过包初始化方法init执行初始化。需要注意的是,如果初始化逻辑存在错误的可能,由于init方法的错误无法被上层捕获,初始化出错时往往直接终止程序启动。
尤其是当初始化中可能存在错误中断的场景,比如:

func init () {
    err = doSomething()
    if err !=nil{
        return 
    }
    goOnDoSomething()
    ...
    ...
}

在启动服务时,一旦err!=nil后面的goOnDoSomething()代码就不会被执行,整个服务可能出现预想不到的情况。
所以大部分情况下最好显示的使用初始化方法或可以使用单例工厂模式来初始化。
gf文档参考:https://goframe.org/pages/viewpage.action?pageId=6357066

4.规范路由

从gfv2.0版本开始,框架的Server组件提供了规范化的路由注册方式,更加适合团队规范化的使用场景,实现了以下特性:

  • 规范化API按照结构化编程设计
  • 规范化API接口方法参数风格定义
  • 更加简化的路由注册与维护
  • 统一接口返回数据格式设计
  • 保障代码与接口文档同步维护
  • 自动的API参数对象化接收与校验
  • 自动生成基于标准OpenAPIv3协议的接口文档
  • 自动生成SwaggerUI页面

gf文档参考:https://goframe.org/pages/viewpage.action?pageId=30736904

5.目录及文件名规范

目录使用驼峰命名,文件名使用下划线分割。

6.gf-cli dao生成


# CLI.
gfcli:
  gen:
    dao:
      - link:            "mysql:root:123456@tcp(127.0.0.1:3306)/gfast-v3" #数据库链接
        tables:          "sys_user_post" #要生成的表,可同时配置多个表
        removePrefix:    "gf_"
        descriptionTag:  true
        noModelComment:  true
        path: "./internal/app/system"   #生成的代码放到对应模块路径

配置好后,在命令行执行:

# 因为我本机上有gf1.0版本的gf-cli所以把gf2.0cli重命名为gf2
gf2 gen dao

执行以上命令后即可在目录./internal/app/system下生成对应的代码。开发过程中请勿修改生成的代码,若改了表结构直接生成覆盖即可。

作者:管理员  创建时间:2023-01-06 13:30
最后编辑:管理员  更新时间:2024-04-22 11:17