概览
本文将使用 beego 框架实现一个 crud 的 api。通过 beego 的脚手架 bee 工具我们将快速地完成项目地初始化和生成相关配置文件。那么现在就开始吧!
快速上手
项目环境配置
请先确保你已安装 beego 和 bee,如果还未安装,请按照 beego 安装指南 和 bee 安装指南。
生成你的项目
bee api api-template
上述命令将在 $GOPATH/src 生成一个样例程序,其中包括 object 和 user 相关的 controller 与 model, 该目录下的文件结构如下所示
|____routers
| |____router.go
|____tests
| |____default_test.go
|____models
| |____object.go
| |____user.go
|____controllers
| |____object.go
| |____user.go
|____conf
| |____app.conf
|____main.go
当我们在使用命令生成 api 项目的时候指定数据库连接 conn 时,bee 就能够根据你的数据库表结构生成模型和 controller。虽然我在 GOPATH 目录下使用 bee 命令,并且指定了 conn ,但是一直在出现下面的错误。
spike:src apple1$ bee api practice-api -conn="root:password@tcp(127.0.0.1:3306)/beego_blog"
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.10.0
2020/05/13 21:35:04 INFO ▶ 0001 Creating API...
create /usr/local/opt/go/src/practice-api
create /usr/local/opt/go/src/practice-api/conf
create /usr/local/opt/go/src/practice-api/controllers
create /usr/local/opt/go/src/practice-api/tests
create /usr/local/opt/go/src/practice-api/conf/app.conf
create /usr/local/opt/go/src/practice-api/main.go
2020/05/13 21:35:04 INFO ▶ 0002 Using 'mysql' as 'driver'
2020/05/13 21:35:04 INFO ▶ 0003 Using 'root:password@tcp(127.0.0.1:3306)/beego_blog' as 'conn'
2020/05/13 21:35:04 INFO ▶ 0004 Using '' as 'tables'
2020/05/13 21:35:04 INFO ▶ 0005 Analyzing database tables...
2020/05/13 21:35:04 FATAL ▶ 0006 Cannot generate application code outside of GOPATH '/usr/local/opt/go' compare with CWD '/usr/local/opt/go/src/practice-api'
最终 bee 生成的项目目录如下所示
.
|____routers
|____tests
|____models
|____controllers
|____conf
| |____app.conf
|____main.go
项目配置
打开 conf/app.conf 文件
appname = my-api
httpport = 8080
httpaddr = 127.0.0.1
runmode = dev
# api 项目不需要模板渲染模板
autorender = false
# http 请求时,允许返回原始请求体数据字节
copyrequestbody = true
# 开启自动化生成文档
EnableDocs = true
db_driver_name = mysql
sqlconn = root:password@/beego_template?charset=utf8&loc=Asia%2FShanghai
模型设计
bee 生成 migration 文件
bee generate migration user
database/migrations/20200429_231223_users.go
package main
import (
"github.com/astaxie/beego/migration"
)
// DO NOT MODIFY
type Users_20200429_231223 struct {
migration.Migration
}
// DO NOT MODIFY
func init() {
m := &Users_20200429_231223{}
m.Created = "20200429_231223"
migration.Register("Users_20200429_231223", m)
}
// Run the migrations
func (m *Users_20200429_231223) Up() {
m.SQL("CREATE TABLE users(id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(255), password VARCHAR(255), email VARCHAR(255))")
}
// Reverse the migrations
func (m *Users_20200429_231223) Down() {
m.SQL("DROP TABLE users")
}
在 models 目录下新建 models.go 和 user.go
models/models.go
package models
import (
"github.com/astaxie/beego/orm"
"fmt"
"github.com/astaxie/beego"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
Id int `json:"id"`
Name string `json:"name"`
Password string `json:"password"`
Email string `json:"email"`
}
func (*User) TableEngine() string {
return engine()
}
func (*User) TableName() string {
return "user"
}
func engine() string {
return "INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci"
}
func init() {
driverName := beego.AppConfig.String("db_driver_name")
sqlconn := beego.AppConfig.String("sqlconn")
err := orm.RegisterDataBase("default", driverName, sqlconn, 30, 200)
if err != nil {
fmt.Println("连接数据库出错, %s", err.Error())
return
}
fmt.Println("成功连接数据库")
orm.RegisterModel(new(User))
}
models/user.go
package models
import (
"github.com/astaxie/beego/orm"
"my-api/utils"
"strconv"
)
func InsertUser(user *User)(int64, error){
orm := orm.NewOrm()
return orm.Insert(user)
}
func GetUser(uid int)(u *User, err error){
orm := orm.NewOrm()
user := &User{ Id: uid }
err = orm.Read(user)
return user, err
}
func DeleteUser(uid int)(b bool, err error) {
orm := orm.NewOrm()
user := &User{ Id: uid }
_, err = orm.Delete(user)
if err != nil {
return false, err
}
return true, err
}
func GetAllUsers(p int, size int) (u utils.Pagination, err error) {
o := orm.NewOrm()
user := new(User)
var users []User
qs := o.QueryTable(user)
count, _ := qs.Count()
_, err = qs.Limit(size).Offset((p - 1) * size).All(&users)
c, _ := strconv.Atoi(strconv.FormatInt(count, 10))
return utils.Paginate(c, p, size, users), err
}
接口设计
error.go 对统一对错误情况进行处理
controllers/error.go
package controllers
import (
"github.com/astaxie/beego"
)
type ErrorController struct {
beego.Controller
}
func (c *ErrorController) Error404() {
c.Data["content"] = "page not found"
}
func (c *ErrorController) Error500() {
c.Data["content"] = "server error"
}
上面的实践并不怎么好,错误处理逻辑隐藏掉了错误信息,在生产环境我们可以这么干,但是在测试和开发环境这使我们很难调试!你可以改进一下错误处理逻辑返回详细的报错信息。
controllers/user.go
package controllers
import (
"my-api/models"
"github.com/astaxie/beego"
"encoding/json"
"my-api/utils"
"strconv"
)
type UserController struct {
beego.Controller
}
// @Title CreateUser
// @Description create user
// @Param body body models.User true "content for user"
// @Success 200 {string} ok
// @Failure 403 body is empty
// @router / [post]
func (this *UserController) Post() {
var user models.User
err := json.Unmarshal(this.Ctx.Input.RequestBody, &user)
if err != nil {
this.Abort("500")
}
models.InsertUser(&user)
this.Data["json"] = "ok"
this.ServeJSON()
}
// @Title GetUser
// @Description get user
// @Param uid path string true "user's id you want to get"
// @Success 200 {object} models.User
// @Failure 403 uid is empty
// @router /:uid [get]
func (this *UserController) Get() {
uid, _ := this.GetInt(":uid")
user, err := models.GetUser(uid)
if err != nil {
this.Data["json"] = user
} else {
this.Abort("500")
}
this.ServeJSON()
}
// @Title DeleteUser
// @Description delete user by id
// @Param uid path string true "user's id you want to delete"
// @Success 200 {string} ok
// @Failure 403 uid is empty
// @router /uid [delete]
func (this *UserController) Delete() {
uid, _ := this.GetInt(":uid")
_, err := models.DeleteUser(uid)
if err != nil {
this.Abort("500")
}
this.Data["json"] = "ok"
this.ServeJSON()
}
// @Title GetAllUsers
// @Description get all users with pagination
// @Param page path string true "the page you want get get"
// @Success 200 {object} models.User
// @Failure 403 uid is empty
// @router /
func (this *UserController) GetAll() {
currentPage, _ := strconv.Atoi(this.Ctx.Input.Query("page"))
if currentPage == 0 {
currentPage = 1
}
d, err := models.GetAllUsers(currentPage, utils.PageSize)
if err != nil {
this.Abort("500")
}
this.Data["json"] = d
this.ServeJSON()
}
你可以注意到我们的接口上有很多类似于 @Title, @Description 等的注解,这些注解可以帮助我们自动生成 api 文档。在使用 json.Unmarshal(this.Ctx.Input.RequestBody, &user) 时, 请确保 app.conf 文件中 copyrequestbody = true。
配置路由
routers/router.go
// @APIVersion 1.0.0
// @Title beego Test API
// @Description beego has a very cool tools to autogenerate documents for your API
// @Contact [email protected]
// @TermsOfServiceUrl http://beego.me/
// @License Apache 2.0
// @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html
package routers
import (
"my-api/controllers"
"github.com/astaxie/beego"
)
func init() {
ns := beego.NewNamespace("/v1",
beego.NSNamespace("/user",
beego.NSInclude(
&controllers.UserController{},
),
),
)
beego.AddNamespace(ns)
}
routers/commentsRouter_controllers.go
package routers
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context/param"
)
func init() {
beego.GlobalControllerRouter["my-api/controllers:UserController"] = append(beego.GlobalControllerRouter["my-api/controllers:UserController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["my-api/controllers:UserController"] = append(beego.GlobalControllerRouter["my-api/controllers:UserController"],
beego.ControllerComments{
Method: "GetAll",
Router: `/`,
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["my-api/controllers:UserController"] = append(beego.GlobalControllerRouter["my-api/controllers:UserController"],
beego.ControllerComments{
Method: "Get",
Router: `/:uid`,
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["my-api/controllers:UserController"] = append(beego.GlobalControllerRouter["my-api/controllers:UserController"],
beego.ControllerComments{
Method: "Delete",
Router: `/uid`,
AllowHTTPMethods: []string{"delete"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
}
工具方法
utils/pagination.go
package utils
var (
PageSize int = 10
)
type Pagination 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 Paginate(count int, page int, pageSize int, list interface{}) Pagination {
tp := count / pageSize
if count%pageSize > 0 {
tp = count/pageSize + 1
}
return Pagination{Page: page, PageSize: pageSize, TotalPage: tp, TotalCount: count, FirstPage: page == 1, LastPage: page == tp, List: list}
}
主函数
package main
import (
_ "my-api/routers"
"github.com/astaxie/beego"
"my-api/controllers"
)
func main() {
# 挂载 api doc
if beego.BConfig.RunMode == "dev" {
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
}
# 注册 error 处理 controller
beego.ErrorController(&controllers.ErrorController{})
beego.Run()
}
总结
最终我们的项目结构为
.
|____routers
| |____commentsRouter_controllers.go
| |____router.go
|____database
| |____migrations
| | |____20200429_231223_users.go
|____go.mod
|____utils
| |____pagination.go
|____go.sum
|____models
| |____models.go
| |____user.go
|____controllers
| |____error.go
| |____user.go
|____conf
| |____app.conf
|____migration
|____main.go