go语言作为c语言同父异母的兄弟在后端服务器开发方面表现出了其惊人的天赋,出生记为高并发、多核心而生。
beego是华人谢大神借鉴tornado、sinatra 和 flask 这三个框架的精髓而设计的一个框架,该框架可以帮助人们来快速开发API、web、以及后端服务应用。
站在巨人的肩膀上,也同时作为学习go的具体实战项目,从beego开始创建一个基于前后端分离的web项目。
[帮助]
- beego官方文档
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接口:文章图片
设计接口API RESTful API的特点
- 基于“资源”,数据也好、服务也好,在RESTFul设计里一切都是资源。
- 无状态。一次调用一般就会返回结果,不存在类似于“打开连接-访问数据-关闭连接”这种依赖于上一次调用的情况。
- URL中通常不出现动词,只有名词
- URL语义清晰、明确
- 使用HTTP的GET、POST、DELETE、PUT来表示对于资源的增删改查
- 使用JSON不使用XML
参考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文档撰写有很多开源的工具,我使用的是
showdoc
(支持api接口编写和数据字典编写),支持在线编写和自己独立部署。设计接口示例:
简要描述:
请求URL:
- banner
请求方式:
/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:无限分级结构的数据,引用自己的主键作为外键
2. 设计beego数据结构模型
- banner表,存储banner信息
字段 类型 空 默认 注释 id bigint 否 主键 name varchar(50) 否 名称 image varchar(255) 否 内容 title varchar(255) 是 NULL 标题 content varchar(255) 是 NULL 内容
- 备注:无
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 string
orm:"-"`` auto
,Id
的 Field 将被视为自增健pk
设置为主键,适用于自定义其他类型为主键null
默认为 NOT NULL,设置null
代表 ALLOW NULLindex
为单个字段增加索引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 表需要有到Post
和Tag
的关系
当设置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 就可以了。
- rel / reverse
- 模型字段与数据库类型的对应
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) |
这里借用官方文档说明下该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 的稳定与健壮
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请求数据结构
示例
2. 根据流程图实现每个接口的业务逻辑type BannerResp struct { Errno string `json:"errno"` Errmsg string `json:"errmsg"` Data interface{} `json:"data"` }
Request 为get参数
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()
都不支持 - 只支持二级解析,一级版本号,二级表示应用模块
3)接口注解func init() { ns := beego.NewNamespace("/api/v1.0", beego.NSNamespace("/banner", beego.NSInclude( &controllers.BannerController{}, ), ), ) beego.AddNamespace(ns) }
示例:
注解详解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
中的id
;body
表示是一个 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
两个参数,使用空格分隔- 请求的路由地址,支持正则和自定义路由
- 请求方法,放在
[]
之中,如果有多个方法,那么使用,
分隔
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", "*")
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,
}))