go语言作为c语言同父异母的兄弟在后端服务器开发方面表现出了其惊人的天赋,出生记为高并发、多核心而生。
beego是华人谢大神借鉴tornado、sinatra 和 flask 这三个框架的精髓而设计的一个框架,该框架可以帮助人们来快速开发API、web、以及后端服务应用。
站在巨人的肩膀上,也同时作为学习go的具体实战项目,从beego开始创建一个基于前后端分离的web项目。
[帮助]
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
创建一个xxxx.com的工程:
$bee api xxxx.com
下面运行该工程,-gendoc=true
表示每次自动化的 build 文档,-downdoc=true
就会自动的下载 swagger 文档查看器:
$bee run -gendoc=true -downdoc=true
在浏览器中输入http://localhost:8080/swagger/
可以看到工程预置的user和object接口:
RESTful API的特点
RESTful风格的接口设计尽量遵循以下原则
参考RESTful API接口设计:
撰写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. 创建数据字典
数据库设计规范
需注意的数据库设计原则可参考
设计数据字典示例
- 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
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.9999Money 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)"`
...
}
对应的反向关系 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 就可以了。
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) |
beego提供一个强大的ORM框架,支持关联查询和SQL查询。
这里借用官方文档说明下该ORM的强大。
已支持数据库驱动:
MySQL:github.com/go-sql-driver/mysql
PostgreSQL:github.com/lib/pq
Sqlite3:github.com/mattn/go-sqlite3
ORM 特性
安装 ORM
go get github.com/astaxie/beego/orm
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_addr string //mysql ip 地址
G_mysql_port string //mysql 端口
G_mysql_dbname string //mysql db name
G_mysql_uname string //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框架下的数据库操作
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
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 [email protected]
package routers
以上是常用的参数,通个bee api project_name
会自动生成,更多参数参考。
2)路由解析
vim routers/router.go
支持的API swagger文档自动生成,对路由设置有一定的要求,其他的方式不会自动解析,即
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 // @Param name query string true "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
成功返回给客户端的信息,共三个参数,之间用空格分开
@Failure
失败返回的信息,包含两个参数,使用空格分隔
@router
两个参数,使用空格分隔
[]
之中,如果有多个方法,那么使用 ,
分隔4)生成swagger文档
bee run -gendoc=true -downdoc=true
,让我们的 API 应用跑起来,-gendoc=true
表示每次自动化的 build
文档,-downdoc=true
就会自动的下载 swagger 文档查看器。
在浏览器中输入下面的URL,就可以看到该文档一开始的API文档了:
http://localhost:8080/swagger/
5)测试请求接口
测试接口可以用go写一个小的测试程序进行测试,或者邀请前端的工程师帮忙测试,当然也有一些工具可以辅助测试。
[GET] /api/v1.0/banner?name=homepage
可以看到返回结果,说明接口已经调试成功了:
{
"errno": "0",
"errmsg": "成功",
"data": {
"list": [
{
"content": "呵护你的每一天",
"image_url":
"static/img/banner2.jpg",
"link": "",
"title": "医佰康"
}
],
"total": 1
}
}
6)可能遇到的问题
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,
}))