使用 orm 实现增删改查 – beego 框架使用笔记

beego ORM 是一个强大的 Go 语言 ORM 框架,目前该框架已支持 MySQL、PostgreSQL、Sqlite3 数据库驱动。

安装 ORM

go get github.com/astaxie/beego/orm
复制代码

初始化

创建项目

使用 bee 工具的安装 api 命令 bee api apiproject
创建 apiproject 项目,具体使用方法查看 beego api 命令

模型关系

# 外键始终在子表上
# 一个用户对应一个简介;一个简介对应一个用户;
one2one:User(子表) -> Profile(主表);one2one:Profile -> User 
复制代码

结构体

type User struct {
    Id          int         `json:"id"`
    Username    string      `json:"username"`
    Password    string      `json:"password"`
    Profile     *Profile    `orm:"rel(one)" json:"profile"` // OneToOne relation
}

type Profile struct {
    Id          int         `json:"id"`
    Gender      int         `json:"gender"`
    Age         int         `json:"age"`
    Address     string      `json:"address"`
    Email       string      `json:"email"`
    User        *User       `orm:"reverse(one)" json:"-"` // 设置一对一反向关系(可选)
}
复制代码
使用标签`orm:"column(id)`对属性进行标注,用于解析。
`orm:"rel(one)"` 表示one2one
`orm:"reverse(one)"` `orm:"reverse(one)"`  标注反向关系
复制代码

database 信息

注册 model:

orm.RegisterModel(new(User), new(Profile))
复制代码

自定义表名:

func (u *User) TableName() string {
    return "users"
}

func (u *Profile) TableName() string {
    return "users_profiles"
}
复制代码

自动建表:

orm.RunSyncdb("default", false, true)
复制代码

数据表结构:

mysql> show tables;
+-------------------+
| Tables_in_play_db |
+-------------------+
| users             |
| users_profiles    |
+-------------------+
2 rows in set (0.01 sec)

mysql> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| username   | varchar(255) | NO   |     |         |                |
| password   | varchar(255) | NO   |     |         |                |
| profile_id | int(11)      | NO   | UNI | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.02 sec)

mysql> desc users_profiles;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | int(11)      | NO   | PRI | NULL    | auto_increment |
| gender  | int(11)      | YES  |     | 0       |                |
| age     | int(11)      | YES  |     | 0       |                |
| address | varchar(255) | YES  |     |         |                |
| email   | varchar(255) | YES  |     |         |                |
+---------+--------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)
复制代码

功能实现

项目参数配置

/apiproject/conf/app.conf

appname = apiproject
httpport = 8090
runmode = dev
autorender = false
copyrequestbody = true
EnableDocs = true
sqlconn = root:123456@/play_db?charset=utf8
复制代码

通用 utils

分页:
/apiproject/utils/page.go

package utils

var (
    PageSize int = 10
)

type Page struct {
    Page        int             `json:"page"`
    PageSize    int             `json:"pageSize"`
    TotalPage   int             `json:"totalPage"`
    TotalCount  int             `json:"totalCount"`
    FirstPage   bool            `json:"firstPage"`
    LastPage    bool            `json:"lastPage"`
    List        interface{}     `json:"list"`
}

func Pagination(count int, page int, pageSize int, list interface{}) Page {
    tp := count / pageSize
    if count%pageSize > 0 {
        tp = count/pageSize + 1
    }
    return Page{Page: page, PageSize: pageSize, TotalPage: tp, TotalCount: count, FirstPage: page == 1, LastPage: page == tp, List: list}
}
复制代码

main 入口

/apiproject/main.go

package main

import (
    _ "apiproject/routers"
    "github.com/astaxie/beego/orm"
    "github.com/astaxie/beego"
    _ "github.com/go-sql-driver/mysql"
)

func init() {
    // 参数1   driverName
    // 参数2   数据库类型
    // 这个用来设置 driverName 对应的数据库类型
    // mysql / sqlite3 / postgres 这三种是默认已经注册过的,所以可以无需设置
    // orm.RegisterDriver("mysql", orm.DRMySQL)

    // ORM 必须注册一个别名为 default 的数据库,作为默认使用。
    // 参数1      数据库的别名,用来在 ORM 中切换数据库使用
    // 参数2      driverName
    // 参数3      对应的链接字符串
    orm.RegisterDataBase("default", "mysql", beego.AppConfig.String("sqlconn"))
}

func main() {
    if beego.BConfig.RunMode == "dev" {
        beego.BConfig.WebConfig.DirectoryIndex = true
        beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
    }

    // 开启 orm 调试模式:开发过程中建议打开,release时需要关闭
    orm.Debug = true

    beego.Run()
}
复制代码

路由实现

RESTful Controller 路由,初始化 namespace:
/apiproject/routers/router.go

// @APIVersion 1.0.0
// @Title apiproject API
// @License Apache 2.0
package routers

import (
    "apiproject/controllers"

    "github.com/astaxie/beego"
)

func init() {
    ns := beego.NewNamespace("/v1",
        beego.NSNamespace("/object",
            beego.NSInclude(
                &controllers.ObjectController{},
            ),
        ),
        beego.NSNamespace("/user",
            beego.NSInclude(
                &controllers.UserController{},
            ),
        ),
    )
    beego.AddNamespace(ns)
}
复制代码

/apiproject/routers/commentsRouter_controllers.go

package routers

import (
    "github.com/astaxie/beego"
    "github.com/astaxie/beego/context/param"
)

func init() {

    beego.GlobalControllerRouter["apiproject/controllers:UserController"] = append(beego.GlobalControllerRouter["apiproject/controllers:UserController"],
        beego.ControllerComments{
            Method: "Post",
            Router: `/`,
            AllowHTTPMethods: []string{"post"},
            MethodParams: param.Make(),
            Filters: nil,
            Params: nil})

    beego.GlobalControllerRouter["apiproject/controllers:UserController"] = append(beego.GlobalControllerRouter["apiproject/controllers:UserController"],
        beego.ControllerComments{
            Method: "GetAll",
            Router: `/`,
            AllowHTTPMethods: []string{"get"},
            MethodParams: param.Make(),
            Filters: nil,
            Params: nil})

    beego.GlobalControllerRouter["apiproject/controllers:UserController"] = append(beego.GlobalControllerRouter["apiproject/controllers:UserController"],
        beego.ControllerComments{
            Method: "Get",
            Router: `/:uid`,
            AllowHTTPMethods: []string{"get"},
            MethodParams: param.Make(),
            Filters: nil,
            Params: nil})

    beego.GlobalControllerRouter["apiproject/controllers:UserController"] = append(beego.GlobalControllerRouter["apiproject/controllers:UserController"],
        beego.ControllerComments{
            Method: "Put",
            Router: `/:uid`,
            AllowHTTPMethods: []string{"put"},
            MethodParams: param.Make(),
            Filters: nil,
            Params: nil})

    beego.GlobalControllerRouter["apiproject/controllers:UserController"] = append(beego.GlobalControllerRouter["apiproject/controllers:UserController"],
        beego.ControllerComments{
            Method: "Delete",
            Router: `/:uid`,
            AllowHTTPMethods: []string{"delete"},
            MethodParams: param.Make(),
            Filters: nil,
            Params: nil})

}
复制代码

controller 实现

通用 controller base:
/apiproject/controllers/base.go

package controllers

import (
    "github.com/astaxie/beego"
)

type BaseController struct {
    beego.Controller
}

// Response 结构体
type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data"`
}

// Error Response 结构体
type ErrResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
}
复制代码

自定义错误提示码:
/apiproject/controllers/code.go

package controllers

var (
    // Common errors
    SUCCESS             = &Errno{Code: 0, Message: "成功"}
    InternalServerError = &Errno{Code: 10001, Message: "内部服务错误"}
    ErrBind             = &Errno{Code: 10002, Message: "参数错误"}

    ErrDatabase       = &Errno{Code: 20001, Message: "数据库错误"}
    ErrToken          = &Errno{Code: 20002, Message: "签发令牌出错"}
    ErrNoPermission   = &Errno{Code: 20003, Message: "无权限"}

    // user errors
    ErrUserNotFound       = &Errno{Code: 20101, Message: "用户未注册"}
    ErrUserExist          = &Errno{Code: 20102, Message: "用户已存在"}
)
复制代码

通用错误处理:
/apiproject/controllers/errno.go

package controllers

import "fmt"

type Errno struct {
    Code    int
    Message string
}

func (err Errno) Error() string {
    return err.Message
}

// Err represents an error
type Err struct {
    Code    int
    Message string
    Err     error
}

func New(errno *Errno, err error) *Err {
    return &Err{Code: errno.Code, Message: errno.Message, Err: err}
}

func (err *Err) Add(message string) error {
    err.Message += " " + message
    return err
}

func (err *Err) Addf(format string, args ...interface{}) error {
    err.Message += " " + fmt.Sprintf(format, args...)
    return err
}

func (err *Err) Error() string {
    return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err)
}

func IsErrUserNotFound(err error) bool {
    code, _ := DecodeErr(err)
    return code == ErrUserNotFound.Code
}

func DecodeErr(err error) (int, string) {
    if err == nil {
        return SUCCESS.Code, SUCCESS.Message
    }

    switch typed := err.(type) {
    case *Err:
        return typed.Code, typed.Message
    case *Errno:
        return typed.Code, typed.Message
    default:
    }

    return InternalServerError.Code, err.Error()
}
复制代码

用户 controller 模块:
/apiproject/controllers/user.go

package controllers

import (
    "apiproject/models"
    "apiproject/utils"
    "encoding/json"
    "strconv"
)

// Operations about Users
type UserController struct {
    BaseController
}

// @Title CreateUser
// @Description create users
// @Param   body        body    models.User true        "body for user content"
// @Success 200 {int} models.User.Id
// @Failure 403 body is empty
// @router / [post]
func (u *UserController) Post() {
    var user models.User
    _ = json.Unmarshal(u.Ctx.Input.RequestBody, &user)
    uid, _ := models.AddUser(user)
    u.Data["json"] = map[string]int64{"uid": uid}
    u.ServeJSON()
}

// @Title GetAll
// @Description get all Users
// @Success 200 {object} models.User
// @router / [get]
func (u *UserController) GetAll() {
    currentPage, _ := strconv.Atoi(u.Ctx.Input.Query("page"))
    if currentPage == 0 {
        currentPage = 1
    }
    pageSize := utils.PageSize
    d , err:= models.GetAllUsers(currentPage,pageSize)
    code, message := DecodeErr(err)

    if err != nil {
        u.Data["json"] = ErrResponse{code, message}
    } else {
        u.Data["json"] = Response{code, message, d}
    }
    u.ServeJSON()
}

// @Title Get
// @Description get user by uid
// @Param   uid     path    string  true        "The key for staticblock"
// @Success 200 {object} models.User
// @Failure 403 :uid is empty
// @router /:uid [get]
func (u *UserController) Get() {
    uid, _ := u.GetInt(":uid")
    if uid > 0 {
        user, err := models.GetUser(uid)
        code, message := DecodeErr(err)
        if err != nil {
            u.Data["json"] = ErrResponse{code, message}
        } else {
            u.Data["json"] = Response{code, message, user}
        }
    }
    u.ServeJSON()
}

// @Title Update
// @Description update the user
// @Param   uid     path    string  true        "The uid you want to update"
// @Param   body        body    models.User true        "body for user content"
// @Success 200 {object} models.User
// @Failure 403 :uid is not int
// @router /:uid [put]
func (u *UserController) Put() {
    uid, _ := u.GetInt(":uid")
    if uid > 0 {
        var user models.User
        _ = json.Unmarshal(u.Ctx.Input.RequestBody, &user)
        uu, err := models.UpdateUser(uid, &user)
        code, message := DecodeErr(err)
        if err != nil {
            u.Data["json"] = ErrResponse{code, message}
        } else {
            u.Data["json"] = Response{code, message, uu}
        }
    }
    u.ServeJSON()
}

// @Title Delete
// @Description delete the user
// @Param   uid     path    string  true        "The uid you want to delete"
// @Success 200 {string} delete success!
// @Failure 403 uid is empty
// @router /:uid [delete]
func (u *UserController) Delete() {
    uid, _ := u.GetInt(":uid")
    b, err := models.DeleteUser(uid)
    code, message := DecodeErr(err)
    if err != nil {
        u.Data["json"] = ErrResponse{code, message}
    } else {
        u.Data["json"] = Response{code, message, b}
    }
    u.ServeJSON()
}
复制代码

model 实现

用户 model 模块,orm 实现 增删改查:
/apiproject/models/user.go

package models

import (
    "apiproject/utils"
    "errors"
    "github.com/astaxie/beego/orm"
    "strconv"
)

type User struct {
    Id          int         `json:"id"`
    Username    string      `json:"username"`
    Password    string      `json:"password"`
    Profile     *Profile    `orm:"rel(one)" json:"profile"` // OneToOne relation
}

type Profile struct {
    Id          int         `json:"id"`
    Gender      int         `json:"gender"`
    Age         int         `json:"age"`
    Address     string      `json:"address"`
    Email       string      `json:"email"`
    User        *User       `orm:"reverse(one)" json:"-"` // 设置一对一反向关系(可选)
}

// 自定义表名
func (u *User) TableName() string {
    return "users"
}

func (u *Profile) TableName() string {
    return "users_profiles"
}

// 新增用户
func AddUser(u User) (id int64, err error) {
    // one2one 插入
    // 创建一个 ormer 对象
    o := orm.NewOrm()

    // 开启事务
    err = o.Begin()

    // 插入主表
    profile := Profile{
        Gender: u.Profile.Gender,
        Age: u.Profile.Age,
        Address: u.Profile.Address,
        Email: u.Profile.Email}
    id, err = o.Insert(&profile)
    if err != nil {
        // 回滚事务
        err = o.Rollback()
    }

    // 插入子表
    user := User{
        Username: u.Username,
        Password: u.Password,
        Profile: &Profile{Id: int(id)}}
    _, err = o.Insert(&user)
    if err != nil {
        // 回滚事务
        err = o.Rollback()
    }

    // 提交事务
    err = o.Commit()
    return id, err
}

// 查询指定用户
func GetUser(uid int) (u *User, err error) {
    o := orm.NewOrm()

    user := &User{Id: uid}
    err = o.Read(user)

    // 已经取得了 Users 对象,查询 UserProfiles
    if user.Profile != nil {
        err = o.Read(user.Profile)
    }

    return user, err
}

// 分页查询用户
func GetAllUsers(p int, size int) (u utils.Page, err error) {
    o := orm.NewOrm()
    user := new(User)
    var users []User
    qs := o.QueryTable(user)
    count, _ := qs.Limit(-1).Count()
    _, err = qs.RelatedSel().Limit(size).Offset((p - 1) * size).All(&users)
    for _, u := range users {
        if u.Profile != nil {
            err = o.Read(u.Profile)
        }
    }
    c, _ := strconv.Atoi(strconv.FormatInt(count, 10))
    return utils.Pagination(c, p, size, users), err
}

// 更新指定用户
func UpdateUser(uid int, uu *User) (a *User, err error) {
    o := orm.NewOrm()
    user := User{Id: uid}
    profile := Profile{Id: uid}

    if o.Read(&user) == nil {

        if uu.Username != "" {
            user.Username = uu.Username
        }
        if uu.Password != "" {
            user.Password = uu.Password
        }

        if o.Read(&profile) == nil {

            if uu.Profile.Age > 0 {
                profile.Age = uu.Profile.Age
            }
            if uu.Profile.Address != "" {
                profile.Address = uu.Profile.Address
            }
            if uu.Profile.Gender == 0 || uu.Profile.Gender == 1 {
                profile.Gender = uu.Profile.Gender
            }
            if uu.Profile.Email != "" {
                profile.Email = uu.Profile.Email
            }
        }

        user.Profile = &profile

        // 开启事务
        err = o.Begin()

        if _, err := o.Update(&user); err != nil {
            return nil, errors.New("修改失败")
        }

        if _, err := o.Update(&profile); err != nil {
            return nil, errors.New("修改失败")
        }
        if err != nil {
            err = o.Rollback()
        } else {
            err = o.Commit()
        }
        return &user, nil
    }

    return nil, err
}

// 删除指定用户
func DeleteUser(uid int) (b bool, err error) {
    // one2one 删除
    // 创建一个 ormer 对象
    o := orm.NewOrm()

    // 开启事务
    err = o.Begin()

    // 删除主表
    profile := Profile{Id: uid}
    _, err = o.Delete(&profile)
    if err != nil {
        // 回滚事务
        err = o.Rollback()
    }

    // 删除子表
    user := User{Id: uid}
    _, err = o.Delete(&user)
    if err != nil {
        // 回滚事务
        err = o.Rollback()
    }

    // 提交事务
    err = o.Commit()
    return b, err
}

// 注册 model
func init(){
    orm.RegisterModel(new(User), new(Profile))
}
复制代码