Develop RESTful API project based on beego

As the half-brother of C language, the go language has shown its amazing talent in the development of back-end servers. It was born for high concurrency and multi-core.

Beego is a framework designed by Chinese Xie Dashen by referring to the essence of the three frameworks of tornado, sinatra and flask. This framework can help people quickly develop API, web, and back-end service applications.

Standing on the shoulders of giants, it is also a specific practical project for learning go, starting from beego to create a web project based on the separation of front and back ends.

[help]

install beego

beego is installed directly through go get. The bee tool is a project created to assist in the rapid development of beego projects. Through bee, you can easily create, hot compile, develop, test, and deploy beego projects.

This project will create an api project based on bee, and the development IDE uses goland (support genuine, you know)

$ go get -u github.com/astaxie/beego
$ go get -u github.com/beego/bee

Create an API project

Create a xxxx.com project:

$bee api xxxx.com

Run the project below, -gendoc=truewhich means that every time you automate the build document, -downdoc=trueyou will automatically download the swagger document viewer:

$bee run -gendoc=true -downdoc=true

Enter in the browser http://localhost:8080/swagger/to see the user and object interfaces preset by the project:
swagger

Design interface API

Features of RESTful API

  • Based on "resources", whether it is data or services, everything is a resource in RESTFul design.
  • no status. One call will generally return the result, and there is no such thing as "opening a connection - accessing data - closing a connection" that depends on the previous call.
  • Verbs usually do not appear in URLs, only nouns
  • URL semantics are clear and unambiguous
  • Use HTTP's GET, POST, DELETE, and PUT to indicate the addition, deletion, modification, and query of resources
  • Use JSON instead of XML

RESTful style interface design should follow the following principles as much as possible.
Refer to RESTful API interface design :

  • Use HTTP verbs to indicate adding, deleting, modifying and querying resources, GET: query, POST: add, PUT: update, DELETE: delete
  • The returned result must use JSON
  • HTTP status codes have specific meanings in REST: 200, 201, 202, 204, 400, 401, 403, 500. For example, 401 means that the user identity authentication failed, and 403 means that your identity verification has passed, but you cannot operate this resource.
  • If an error occurs, return an error code
  • API must have the concept of version, v1, v2, v3
  • Use Token tokens for user identity verification and permission grading instead of cookies
  • Case insensitive in url, do not appear in uppercase letters
  • Use - instead of _ for string concatenation in URL paths
  • There is a beautiful document

Writing API documents
There are many open source tools for writing API documents. I use them showdoc(supporting API interface writing and data dictionary writing), which support online writing and independent deployment.

Example design interface:

A brief description:

  • banner

Request URL:

  • /api/v1.0/banner

Request method:

  • GET

parameter:

parameter name required type illustrate
name yes string banner name
banner name parameter
front page homepage
page two page2
page three page3

return example

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

Return parameter description

parameter name type illustrate
total int The total number of banners
image_url string The map's address
title string banner title
content string banner text

Remark

  • For more return error codes, please see the error code description on the home page

Design data model

If the interface document is to sort out all the functions in the project and is the link of front-end and back-end development, then the data model is the cornerstone of the entire project development process. The quality of the data model design is directly related to the pros and cons of the project, including Maintainability, rationality of architecture, and multi-module collaboration.
1. Create a data dictionary

Database Design Specifications
For the database design principles to be noted, please refer to

  • Rule 1: Figure out what is the nature of the application to be developed (OLTP or OPAP)?
  • Rule 2: Divide your data into logical chunks to make things easier
  • Rule 3: Don't Overuse "Rule 2"
  • Rule 4: Treat duplicate, inconsistent data as your worst enemy
  • Rule 5: Beware of data separated by delimiters, they violate "fields cannot be subdivided"
  • Rule 6: Beware of columns that are only partially dependent on the primary key
  • Rule 7: Choose derived columns carefully
  • Rule 8: If performance is critical, don't be stubborn about avoiding redundancy
  • Rule 9: Multidimensional data is an aggregation of disparate data
  • Rule 10: Unify the design of those tables that have the characteristics of "name-value table"
  • Rule 11: For infinitely hierarchical data, refer to your own primary key as a foreign key

Design Data Dictionary Example

  • banner table, storing banner information
field type null default note
id bigint no primary key
name varchar(50) no name
image varchar(255) no content
title varchar(255) yes NULL title
content varchar(255) yes NULL content
  • Remarks: None

2. Design the beego data structure model
Beego's MVC model is divided into controllers, models, and views from the engineering structure; the routers directory provides the routing information of the interface.

controllers: the specific implementation of interface services, usually the specific implementation of the business;
models: data models, often directly related to the design of the database;

When designing the data structure of beego, the table structure creation and inspection of the ORM framework is realized by passing in the initial value. For details, please refer to the next section

vim models/model.go

Add the following

/* 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. Detailed explanation of beegode's ORM model definition

  • The default table name rules, use camelCase to snake. You can also func (u *User) TableName() string {return "auth_user"}customize the table name.
AuthUser -> auth_user
Auth_User -> auth__user
DB_AuthUser -> d_b__auth_user
  • Custom index (refer to the official document for details )
  • Custom engine (only MYSQL is supported, please refer to the official document for details )
  • Setting parameters
    • orm:"null;rel(fk)"Use to ;separate , if there are multiple settings, use ,to separate
    • ignore field AnyField stringorm:"-"``
    • auto, Idthe Field will be regarded as self-increasing
    • pkSet as the primary key, suitable for customizing other types of primary keys
    • nullThe default is NOT NULL, setting nullmeans ALLOW NULL
    • indexAdd an index to a single field
    • uniqueAdd uniquekeys
    • columnSet the name of the db field for the field,Name string `orm:"column(user_name)"`
    • sizeThe string type field defaults to varchar(255),Title string `orm:"size(60)"`
    • digits / decimalsSet float32, float64 floating-point precision, total length 12 4 digits after the decimal point eg: 99999999.9999
    Money float64 `orm:"digits(12);decimals(4)"`
    
    • auto_now / auto_now_add
      auto_nowThe time is automatically updated every time the model is saved, and
      auto_now_addthe time is only set when the model is saved for the first time
    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)"`
    
    • defaultSet a default value for the field, the type must conform to (currently only used for default values ​​​​when cascading deletes),Status int `orm:"default(1)"`
  • Table relationship set
    foreign key always on child table
    • rel / reverse
      RelOneToOne:

      type User struct {
          ...
          Profile *Profile `orm:"null;rel(one);on_delete(set_null)"`
          ...
      }
      

      The corresponding reverse relation RelReverseOne:

      type Profile struct {
          ...
          User *User `orm:"reverse(one)"`
          ...
      }
      

      RelForeignKey:

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

      The corresponding reverse relationship RelReverseMany:

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

      RelManyToMany:

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

      The corresponding reverse relationship RelReverseMany:

      type Tag struct {
          ...
          Posts []*Post `orm:"reverse(many)"`
          ...
      }
      
    • rel_table / rel_through
      This setting targets relational fields orm:"rel(m2m)"of

      rel_tableSet the name of the automatically generated m2m relationship table.
      rel_throughIf you want to use a custom m2m relationship table in the m2m relationship,
      set its name through this, the format is pkg.path.ModelName
      eg: app.models.PostTagRel
      PostTagRel table needs to have Posta Tagrelationship with and When
      it is set, it will be ignoredrel_tablerel_through

      Setting method:
      orm:"rel(m2m);rel_table(the_table_name)"
      orm:"rel(m2m);rel_through(pkg.path.ModelName)"

    • on_delete sets how to deal with the relationship field when the corresponding rel relationship is deleted.

      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
      

      Related examples about on_delete

      type User struct {
         Id int
         Name string
      }
      
      type Post struct {
         Id int
         Title string
         User *User `orm:"rel(fk)"`
      }
      

      Suppose Post -> User is a ManyToOne relationship, that is, a foreign key.

      o.Filter(“Id”, 1).Delete()
      will delete the User whose Id is 1, and also delete the Post published by it

      If you don't want to delete, you need to set set_null

      type Post struct {
         Id int
         Title string
         User *User `orm:"rel(fk);null;on_delete(set_null)"`
      }
      

      At this time, deleting User will only set the corresponding Post.user_id to NULL

      Of course, sometimes for the sake of high performance, it doesn't matter if you save more data, but the problem of batch deletion > is the problem.

      type Post struct {
         Id int
         Title string
         User *User `orm:"rel(fk);null;on_delete(do_nothing)"`
      }
      

      Then as long as you delete it, you don't need to operate Post.

  • Correspondence between model fields and database types
    MySQL (Sqlite3 and PostgreSQL, please refer to )
go mysql
int, int32 - when auto is set or the name is Id integer AUTO_INCREMENT
int64 - when auto is set or the name is Id bigint AUTO_INCREMENT
uint, uint32 - when auto is set or the name is Id integer unsigned AUTO_INCREMENT
uint64 - when auto is set or the name is Id bigint unsigned AUTO_INCREMENT
bool bool
string - defaults to size 255 varchar(size)
string - when type(char) is set char(size)
string - when type(text) is set longtext
time.Time - when the type is set to date date
time.Time datetime
byte tinyint unsigned
rune integer
int integer
you8 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_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 [email protected]
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) Test request
interface The test interface can be tested by writing a small test program in go, or by inviting front-end engineers to help with the test. Of course, there are also some tools that can assist in the test.

  • Recommend an interface testing tool postman
[GET] /api/v1.0/banner?name=homepage

You can see the returned result, indicating that the interface has been debugged successfully:

{
    "errno": "0",
    "errmsg": "成功",
    "data": {
        "list": [
            {
                "content": "呵护你的每一天",
                "image_url": 
                "static/img/banner2.jpg",
                "link": "",
                "title": "医佰康"
            }
        ],
        "total": 1
    }
}

6) Possible problems

  • API adds CORS support
    ctx.Output.Header("Access-Control-Allow-Origin", "*")

problems encountered

  • packets.go:36: unexpected EOF
    Use DB.SetConnMaxLifetime(time.Second) to set the maximum connection reuse time, 3~10 seconds is enough. orm basically has related settings.
    Reference address: https://github.com/go-sql-driver/mysql/issues/674

  • API adds CORS support
    ctx.Output.Header("Access-Control-Allow-Origin", "*")

Add the following information to the response stream header of the http request

rw.Header().Set(“Access-Control-Allow-Origin”, “*”)

rw is the http.ResponseWriter object

2. Add routing filter to 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,
    }))

Guess you like

Origin blog.csdn.net/rabbit0206/article/details/104200044