基于beego开发RESTfulAPI项目

go语言作为c语言同父异母的兄弟在后端服务器开发方面表现出了其惊人的天赋,出生记为高并发、多核心而生。
beego是华人谢大神借鉴tornado、sinatra 和 flask 这三个框架的精髓而设计的一个框架,该框架可以帮助人们来快速开发API、web、以及后端服务应用。
站在巨人的肩膀上,也同时作为学习go的具体实战项目,从beego开始创建一个基于前后端分离的web项目。
[帮助]

  • beego官方文档
    beego案例
安装beego beego直接通过go get安装,bee 工具是一个为了协助快速开发 beego 项目而创建的项目,通过 bee 您可以很容易的进行 beego项目的创建、热编译、开发、测试、和部署。
本项目将基于bee创建一个api工程,开发IDE采用goland(支持正版,你懂得)
$ go get -u github.com/astaxie/beego $ go get -u github.com/beego/bee

创建API工程 创建一个xxxx.com的工程:
$bee api xxxx.com

下面运行该工程,-gendoc=true 表示每次自动化的 build 文档,-downdoc=true 就会自动的下载 swagger 文档查看器:
$bee run -gendoc=true -downdoc=true

在浏览器中输入http://localhost:8080/swagger/可以看到工程预置的user和object接口:
基于beego开发RESTfulAPI项目
文章图片

设计接口API RESTful API的特点
  • 基于“资源”,数据也好、服务也好,在RESTFul设计里一切都是资源。
  • 无状态。一次调用一般就会返回结果,不存在类似于“打开连接-访问数据-关闭连接”这种依赖于上一次调用的情况。
  • URL中通常不出现动词,只有名词
  • URL语义清晰、明确
  • 使用HTTP的GET、POST、DELETE、PUT来表示对于资源的增删改查
  • 使用JSON不使用XML
RESTful风格的接口设计尽量遵循以下原则
参考RESTful API接口设计:
  • 使用HTTP动词表示增删改查资源, GET:查询,POST:新增,PUT:更新,DELETE:删除
  • 返回结果必须使用JSON
  • HTTP状态码,在REST中都有特定的意义:200,201,202,204,400,401,403,500。比如401表示用户身份认证失败,403表示你验证身份通过了,但这个资源你不能操作
  • 如果出现错误,返回一个错误码
  • API必须有版本的概念,v1,v2,v3
  • 使用Token令牌来做用户身份的校验与权限分级,而不是Cookie
  • url中大小写不敏感,不要出现大写字母
  • 使用 - 而不是使用 _ 做URL路径中字符串连接
  • 有一份漂亮的文档
撰写API文档
API文档撰写有很多开源的工具,我使用的是showdoc(支持api接口编写和数据字典编写),支持在线编写和自己独立部署。
设计接口示例:
简要描述:
  • banner
请求URL:
  • /api/v1.0/banner
请求方式:
  • GET
参数:
参数名 必选 类型 说明
name string banner名
banner名 参数
首页 homepage
页面二 page2
页面三 page3
返回示例
{ "errno": 0, "errmsg" "成功", "data": { "total": 1, "list": [ { "image_url": "http://", "title": "", "content": "!" "link": "http://" } ] } }

返回参数说明
参数名 类型 说明
total int banner的总数量
image_url string 图片地址
title string banner标题
content string banner文本
备注
  • 更多返回错误代码请看首页的错误代码描述
设计数据模型 如果说接口文档是是对项目中所有功能的的梳理,是前后端开发的纽带,那么数据模型则是整个项目开发过程中的基石,数据模型设计的好坏直接关系到项目的优劣,包括可维护性、架构合理性、多模块的协作性。
1. 创建数据字典
数据库设计规范
需注意的数据库设计原则可参考
  • 规则 1:弄清楚将要开发的应用程序是什么性质的(OLTP 还是 OPAP)?
  • 规则 2:将你的数据按照逻辑意义分成不同的块,让事情做起来更简单
  • 规则 3:不要过度使用 “规则 2”
  • 规则 4:把重复、不统一的数据当成你最大的敌人来对待
  • 规则 5:当心被分隔符分割的数据,它们违反了“字段不可再分”
  • 规则 6:当心那些仅仅部分依赖主键的列
  • 规则 7:仔细地选择派生列
  • 规则 8:如果性能是关键,不要固执地去避免冗余
  • 规则 9:多维数据是各种不同数据的聚合
  • 规则 10:将那些具有“名值表”特点的表统一起来设计
  • 规则 11:无限分级结构的数据,引用自己的主键作为外键
设计数据字典示例
  • banner表,存储banner信息
字段 类型 默认 注释
id bigint 主键
name varchar(50) 名称
image varchar(255) 内容
title varchar(255) NULL 标题
content varchar(255) NULL 内容
  • 备注:无
2. 设计beego数据结构模型
beego的MVC模型从工程结构上分为controllers、models、views;routers目录提供了接口的路由信息。
controllers:接口服务的具体实现,通常为业务的具体实现;
models:数据模型,往往和数据库的设计直接相关;
beego的数据结构设计时,通过传入初始值从而实现ORM框架的表结构创建及检查,具体可以参考下一小节
vim models/model.go

添加以下内容
/* table_name = banner */ type Banner struct { Id int64`json:"user_id"`//banner id Name string`orm:"size(50)"json:"name"`//banner name Image string`orm:"size(255)"json:"image"`//banner image url Title string`orm:"size(255)"json:"title"`//banner titile Content string `orm:"size(255)"json:"content"`//banner content }

3. beegode的ORM模型定义详解
  • 默认的表名规则,使用驼峰转蛇形。也可以通过func (u *User) TableName() string {return "auth_user"}自定义表名。
AuthUser -> auth_user Auth_User -> auth__user DB_AuthUser -> d_b__auth_user

  • 自定义索引(具体参考官方文档))
  • 自定义引擎(仅支持MYSQL,具体参考官方文档)
  • 设置参数
    • orm:"null; rel(fk)" 多个设置间使用 ; 分隔,设置的值如果是多个,使用 , 分隔
    • 忽略字段 AnyField stringorm:"-"``
    • autoId 的 Field 将被视为自增健
    • pk 设置为主键,适用于自定义其他类型为主键
    • null 默认为 NOT NULL,设置 null 代表 ALLOW NULL
    • index 为单个字段增加索引
    • unique 为单个字段增加 unique
    • column 为字段设置 db 字段的名称,Name string `orm:"column(user_name)"`
    • size string 类型字段默认为 varchar(255),Title string `orm:"size(60)"`
    • digits / decimals 设置 float32, float64 类型的浮点精度,总长度 12 小数点后 4 位 eg: 99999999.9999
    Money float64 `orm:"digits(12); decimals(4)"`

    • auto_now / auto_now_add
      auto_now 每次 model 保存时都会对时间自动更新
      auto_now_add 第一次保存时才设置时间
    Created time.Time >`orm:"auto_now_add; type(datetime)"` Updated time.Time >`orm:"auto_now; type(datetime)"`

    • type
    设置为 date 时,time.Time 字段的对应 db >类型使用 date Created time.Time >`orm:"auto_now_add; type(date)"` 设置为 datetime 时,time.Time 字段的对应 db >类型使用 datetime Created time.Time >`orm:"auto_now_add; type(datetime)"`

    • default 为字段设置默认值,类型必须符合(目前仅用于级联删除时的默认值), Status int `orm:"default(1)"`
  • 表关系设置
    外键始终在子表上
    • rel / reverse
      RelOneToOne:
      type User struct { ... Profile *Profile `orm:"null; rel(one); on_delete(set_null)"` ... }

      【基于beego开发RESTfulAPI项目】对应的反向关系 RelReverseOne:
      type Profile struct { ... User *User `orm:"reverse(one)"` ... }

      RelForeignKey:
      type Post struct { ... User *User `orm:"rel(fk)"` // RelForeignKey relation ... }

      对应的反向关系 RelReverseMany:
      type User struct { ... Posts []*Post `orm:"reverse(many)"` // fk 的反向关系 ... }

      RelManyToMany:
      type Post struct { ... Tags []*Tag `orm:"rel(m2m)"` // ManyToMany relation ... }

      对应的反向关系 RelReverseMany:
      type Tag struct { ... Posts []*Post `orm:"reverse(many)"` ... }

    • rel_table / rel_through
      此设置针对 orm:"rel(m2m)" 的关系字段
      rel_table 设置自动生成的 m2m 关系表的名称
      rel_through 如果要在 m2m 关系中使用自定义的 m2m 关系表
      通过这个设置其名称,格式为 pkg.path.ModelName
      eg: app.models.PostTagRel
      PostTagRel 表需要有到 PostTag 的关系
      当设置 rel_table 时会忽略 rel_through
      设置方法:
      orm:"rel(m2m); rel_table(the_table_name)"
      orm:"rel(m2m); rel_through(pkg.path.ModelName)"
    • on_delete 设置对应的 rel 关系删除时,如何处理关系字段。
      cascade级联删除(默认值) set_null设置为 NULL,需要设置 null = true set_default设置为默认值,需要设置 default 值 do_nothing什么也不做,忽略

      type User struct { ... Profile *Profile `orm:"null; rel(one); on_delete(set_null)"` ... } type Profile struct { ... User *User `orm:"reverse(one)"` ... }// 删除 Profile 时将设置 User.Profile 的数据库字段为 NULL

      关于 on_delete 的相关例子
      type User struct { Id int Name string }type Post struct { Id int Title string User *User `orm:"rel(fk)"` }

      假设 Post -> User 是 ManyToOne 的关系,也就是外键。
      o.Filter(“Id”, 1).Delete()
      这个时候即会删除 Id 为 1 的 User 也会删除其发布的 Post
      不想删除的话,需要设置 set_null
      type Post struct { Id int Title string User *User `orm:"rel(fk); null; on_delete(set_null)"` }

      那这个时候,删除 User 只会把对应的 Post.user_id 设置为 NULL
      当然有时候为了高性能的需要,多存点数据无所谓啊,造成批量删除>才是问题。
      type Post struct { Id int Title string User *User `orm:"rel(fk); null; on_delete(do_nothing)"` }

      那么只要删除的时候,不操作 Post 就可以了。
  • 模型字段与数据库类型的对应
    MySQL (Sqlite3和PostgreSQL,请参考)
go mysql
int, int32 - 设置 auto 或者名称为 Id 时 integer AUTO_INCREMENT
int64 - 设置 auto 或者名称为 Id 时 bigint AUTO_INCREMENT
uint, uint32 - 设置 auto 或者名称为 Id 时 integer unsigned AUTO_INCREMENT
uint64 - 设置 auto 或者名称为 Id 时 bigint unsigned AUTO_INCREMENT
bool bool
string - 默认为 size 255 varchar(size)
string - 设置 type(char) 时 char(size)
string - 设置 type(text) 时 longtext
time.Time - 设置 type 为 date 时 date
time.Time datetime
byte tinyint unsigned
rune integer
int integer
int8 tinyint
int16 smallint
int32 integer
int64 bigint
uint integer unsigned
uint8 tinyint unsigned
uint16 smallint unsigned
uint32 integer unsigned
uint64 bigint unsigned
float32 double precision
float64 double precision
float64 - 设置 digits, decimals 时 numeric(digits, decimals)
Mysql数据库连接 beego提供一个强大的ORM框架,支持关联查询和SQL查询。
这里借用官方文档说明下该ORM的强大。
已支持数据库驱动:
MySQL:github.com/go-sql-driver/mysql PostgreSQL:github.com/lib/pq Sqlite3:github.com/mattn/go-sqlite3

ORM 特性
  • 支持 Go 的所有类型存储
  • 轻松上手,采用简单的 CRUD 风格
  • 自动 Join 关联表
  • 跨数据库兼容查询
  • 允许直接使用 SQL 查询/映射
  • 严格完整的测试保证 ORM 的稳定与健壮
安装 ORM
go get github.com/astaxie/beego/orm

安装mysql驱动
go get github.com/go-sql-driver/mysql

数据库连接
1. 配置数据库连接参数
vim conf/app.conf

添加以下内容
#配置数据库连接参数 mysqladdr = "127.0.0.1" mysqlport = 3306 mysqldbname = "database_name" mysqlusername = "root" mysqlpassword = "123456" # 设置最大空闲连接 mysqlmaxIdle = 30 # 设置最大数据库连接 (go >= 1.2) mysqlmaxConn = 30

2. 解析配置文件参数
vim utils/config.go

添加以下内容
var ( G_mysql_addrstring //mysql ip 地址 G_mysql_portstring //mysql 端口 G_mysql_dbname string //mysql db name G_mysql_unamestring //mysql username G_mysql_passwd string //mysql password G_mysql_maxidle int//mysql maxidle G_mysql_maxconn int// mysql maxconn )func InitConfig() { //从配置文件读取配置信息 appconf, err := config.NewConfig("ini", "./conf/app.conf") if err != nil { beego.Debug(err) return } G_mysql_addr = appconf.String("mysqladdr") G_mysql_port = appconf.String("mysqlport") G_mysql_dbname = appconf.String("mysqldbname") G_mysql_uname = appconf.String("mysqlusername") G_mysql_passwd = appconf.String("mysqlpassword") G_mysql_maxidle = appconf.Int("mysqlmaxIdle") G_mysql_maxconn = appconf.Int("mysqlmaxConn") return }func init() { InitConfig() }

3. 加载Mysql驱动,初始化数据库
vim models/model.go

添加以下内容
import ( "github.com/astaxie/beego/orm" "backend/utils" _ "github.com/go-sql-driver/mysql" )func init() { orm.RegisterDriver("mysql", orm.DRMySQL) // set default database orm.RegisterDataBase("default", "mysql", utils.G_mysql_uname+":"+utils.G_mysql_passwd+"@tcp("+utils.G_mysql_addr+":"+utils.G_mysql_port+")/"+utils.G_mysql_dbname+"?charset=utf8mb4", utils.G_mysql_maxidle, utils.G_mysql_maxconn) //注册model orm.RegisterModel(new(Banner)) // create table //第二个参数是强制更新数据库 //第三个参数是如果没有则同步 orm.RunSyncdb("default", false, true) }

4. ORM框架下的数据库操作
API接口功能实现 1. 设计基于接口的Request和Response请求数据结构
示例
type BannerResp struct { Errno string `json:"errno"` Errmsg string `json:"errmsg"` Data interface{} `json:"data"` }

Request 为get参数

2. 根据流程图实现每个接口的业务逻辑
graph LR start[获取参数] --> conditionA{检查参数} conditionA -- 参数OK --> conditionB{读取数据库} conditionA -- 参数错误 --> returnB[返回参数错误] conditionB -- 成功 --> returnA[返回json data] conditionB -- 失败 --> returnC[返回数据库查询失败] returnA --> stop[结束] returnB --> stop[结束] returnC --> stop[结束]

3. ORM框架下的数据库操作
4. 创建路由及自动生成API注解
beego支持通过注解自动生成基于swagger的API。
1)全局设置
  • 配置文档开关
vim conf/app.conf

添加一下内容
EnableDocs = true

  • 设置全局路由doc信息
vim routers/router.go

在最顶部添加:
// @APIVersion 1.0.0 // @Title mobile API // @Description mobile has every tool to get any job done, so codename for the new mobile APIs. // @Contact astaxie@gmail.com package routers

以上是常用的参数,通个bee api project_name会自动生成,更多参数参考。
2)路由解析
vim routers/router.go

支持的API swagger文档自动生成,对路由设置有一定的要求,其他的方式不会自动解析,即
  • namespace+Include 的写法,对于beego.NSRouter()都不支持
  • 只支持二级解析,一级版本号,二级表示应用模块
示例:
func init() { ns := beego.NewNamespace("/api/v1.0", beego.NSNamespace("/banner", beego.NSInclude( &controllers.BannerController{}, ), ), ) beego.AddNamespace(ns) }

3)接口注解
示例:
vim controllers/banner.go

添加接口注解内容
// @Title Get the Banners // @Description get the banners of given name // @Success 200 {object} controllers.BannerResp // @Paramnamequerystringtrue"banner position" // @Failure 400 BAD REQUEST // @Failure 500 INTERNAL SERVER ERROR // @router / [get]

注解详解
  • @Title 接口名,空格之后的内容全部解析为 title
  • @Description 接口描述,空格之后的内容全部解析为 Description
  • @Param 传入参数,共五列,使用空格或者 tab 分割
    • 参数名
    • 参数类型,可以有的值是 formData、query、path、body、header。formData 表示是 post 请求的数据;query 表示带在 url 之后的参数;path 表示请求路径上得参数,比如/user/:id中的idbody 表示是一个 raw 数据请求;header 表示带在 header 信息中得参数,比如"Content-Type":"application/json"
    • 参数类型, int,object(swagger会表示为json对象)
    • 该参数是否必须,true,false
    • 参数注释
  • @Success 成功返回给客户端的信息,共三个参数,之间用空格分开
    • status code
    • 返回的类型,必须使用 {} 包含,比如 {int},{object}
    • 返回的对象或者字符串信息,例如{object} 类型, models.ZDTProduct.ProductList 就表示 /models/ZDTProduct 目录下的 ProductList 对象
  • @Failure 失败返回的信息,包含两个参数,使用空格分隔
    • status code
    • 错误信息
  • @router 两个参数,使用空格分隔
    • 请求的路由地址,支持正则和自定义路由
    • 请求方法,放在 []之中,如果有多个方法,那么使用 , 分隔
4)生成swagger文档
bee run -gendoc=true -downdoc=true,让我们的 API 应用跑起来,-gendoc=true 表示每次自动化的 build 文档,-downdoc=true 就会自动的下载 swagger 文档查看器。
在浏览器中输入下面的URL,就可以看到该文档一开始的API文档了:
http://localhost:8080/swagger/

5)测试请求接口
测试接口可以用go写一个小的测试程序进行测试,或者邀请前端的工程师帮忙测试,当然也有一些工具可以辅助测试。
  • 推荐一款接口测试工具postman
[GET] /api/v1.0/banner?name=homepage

可以看到返回结果,说明接口已经调试成功了:
{ "errno": "0", "errmsg": "成功", "data": { "list": [ { "content": "呵护你的每一天", "image_url": "static/img/banner2.jpg", "link": "", "title": "医佰康" } ], "total": 1 } }

6)可能遇到的问题
  • API 增加 CORS 支持
    ctx.Output.Header("Access-Control-Allow-Origin", "*")
遇到的问题
  • packets.go:36: unexpected EOF
    使用DB.SetConnMaxLifetime(time.Second)设置连接最大复用时间,3~10秒即可。orm基本上都有相关的设置。
    参考地址:https://github.com/go-sql-driver/mysql/issues/674
  • API 增加 CORS 支持
    ctx.Output.Header("Access-Control-Allow-Origin", "*")
在http请求的响应流头部加上如下信息
rw.Header().Set(“Access-Control-Allow-Origin”, “*”)
rw是http.ResponseWriter对象
2、Beego中添加路由过滤器
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{ AllowAllOrigins:true, AllowMethods:[]string{"*"}, AllowHeaders:[]string{"Origin", "Authorization", "Access-Control-Allow-Origin"}, ExposeHeaders:[]string{"Content-Length", "Access-Control-Allow-Origin"}, AllowCredentials: true, }))

    推荐阅读