基于beego开发RESTfulAPI项目

go语言作为c语言同父异母的兄弟在后端服务器开发方面表现出了其惊人的天赋,出生记为高并发、多核心而生。

beego是华人谢大神借鉴tornado、sinatra 和 flask 这三个框架的精髓而设计的一个框架,该框架可以帮助人们来快速开发API、web、以及后端服务应用。

站在巨人的肩膀上,也同时作为学习go的具体实战项目,从beego开始创建一个基于前后端分离的web项目。

[帮助]

安装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接口:
swagger

设计接口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

参数:

参数名必选类型说明
namestringbanner名
banner名参数
首页homepage
页面二page2
页面三page3

返回示例

 {
   "errno": 0,
   "errmsg" "成功",
   "data": {
     "total": 1,
     "list":
	  [
		{
		"image_url": "http://",
		"title": "",
		"content": "!"
		"link": "http://"
		}
	  ]
   }
 }

返回参数说明

参数名类型说明
totalintbanner的总数量
image_urlstring图片地址
titlestringbanner标题
contentstringbanner文本

备注

  • 更多返回错误代码请看首页的错误代码描述

设计数据模型

如果说接口文档是是对项目中所有功能的的梳理,是前后端开发的纽带,那么数据模型则是整个项目开发过程中的基石,数据模型设计的好坏直接关系到项目的优劣,包括可维护性、架构合理性、多模块的协作性。
1. 创建数据字典

数据库设计规范
需注意的数据库设计原则可参考

  • 规则 1:弄清楚将要开发的应用程序是什么性质的(OLTP 还是 OPAP)?
  • 规则 2:将你的数据按照逻辑意义分成不同的块,让事情做起来更简单
  • 规则 3:不要过度使用 “规则 2”
  • 规则 4:把重复、不统一的数据当成你最大的敌人来对待
  • 规则 5:当心被分隔符分割的数据,它们违反了“字段不可再分”
  • 规则 6:当心那些仅仅部分依赖主键的列
  • 规则 7:仔细地选择派生列
  • 规则 8:如果性能是关键,不要固执地去避免冗余
  • 规则 9:多维数据是各种不同数据的聚合
  • 规则 10:将那些具有“名值表”特点的表统一起来设计
  • 规则 11:无限分级结构的数据,引用自己的主键作为外键

设计数据字典示例

  • banner表,存储banner信息
字段类型默认注释
idbigint主键
namevarchar(50)名称
imagevarchar(255)内容
titlevarchar(255)NULL标题
contentvarchar(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)"`
          ...
      }
      

      对应的反向关系 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,请参考)
gomysql
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
boolbool
string - 默认为 size 255varchar(size)
string - 设置 type(char) 时char(size)
string - 设置 type(text) 时longtext
time.Time - 设置 type 为 date 时date
time.Timedatetime
bytetinyint unsigned
runeinteger
intinteger
int8tinyint
int16smallint
int32integer
int64bigint
uintinteger unsigned
uint8tinyint unsigned
uint16smallint unsigned
uint32integer unsigned
uint64bigint unsigned
float32double precision
float64double 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_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框架下的数据库操作

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
// @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中的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,
    }))
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值