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接口编写和数据字典编写),支持在线编写和自己独立部署。设计接口示例:
/api/v1.0/banner
{
"errno": 0,
"errmsg" "成功",
"data": {
"total": 1,
"list":
[
{
"image_url": "http://",
"title": "",
"content": "!"
"link": "http://"
}
]
}
}
设计数据模型 如果说接口文档是是对项目中所有功能的的梳理,是前后端开发的纽带,那么数据模型则是整个项目开发过程中的基石,数据模型设计的好坏直接关系到项目的优劣,包括可维护性、架构合理性、多模块的协作性。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
orm:"null;
rel(fk)";
,AnyField stringautoIdpknullnullindexuniqueuniquecolumnName string `orm:"column(user_name)"`sizeTitle string `orm:"size(60)"`digits / decimalsMoney float64 `orm:"digits(12);
decimals(4)"`
auto_now / auto_now_addauto_nowauto_now_addCreated 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)"`
defaultStatus int `orm:"default(1)"`type User struct {
...
Profile *Profile `orm:"null;
rel(one);
on_delete(set_null)"`
...
}
type Profile struct {
...
User *User `orm:"reverse(one)"`
...
}
type Post struct {
...
User *User `orm:"rel(fk)"` // RelForeignKey relation
...
}
type User struct {
...
Posts []*Post `orm:"reverse(many)"` // fk 的反向关系
...
}
type Post struct {
...
Tags []*Tag `orm:"rel(m2m)"` // ManyToMany relation
...
}
type Tag struct {
...
Posts []*Post `orm:"reverse(many)"`
...
}
orm:"rel(m2m)"rel_tablerel_throughpkg.path.ModelNameapp.models.PostTagRelPostTagrel_tablerel_throughorm:"rel(m2m);
rel_table(the_table_name)"orm:"rel(m2m);
rel_through(pkg.path.ModelName)"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
type User struct {
Id int
Name string
}type Post struct {
Id int
Title string
User *User `orm:"rel(fk)"`
}
type Post struct {
Id int
Title string
User *User `orm:"rel(fk);
null;
on_delete(set_null)"`
}
type Post struct {
Id int
Title string
User *User `orm:"rel(fk);
null;
on_delete(do_nothing)"`
}
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请求数据结构
示例
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文档自动生成,对路由设置有一定的要求,其他的方式不会自动解析,即
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@Description@ParamformData、query、path、body、header。formDatapostquerypath/user/:ididbodyheader"Content-Type":"application/json"int,objectjsontrue,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写一个小的测试程序进行测试,或者邀请前端的工程师帮忙测试,当然也有一些工具可以辅助测试。
- 推荐一款接口测试工具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)可能遇到的问题
ctx.Output.Header("Access-Control-Allow-Origin", "*")
遇到的问题
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,
}))