Gin Practice Serialized 3 to Build Blog API’s (2)

  golang, mysql

API’s, Models for Tag

Project address:https://github.com/EDDYCJY/go …

This major section will cover the following knowledge points:

  1. gin: Golang’s micro-frame with excellent performance
  2. beego-validation: beego’s form validation library used in this section,Chinese document
  3. gorm, a developer-friendly ORM framework,English document
  4. com, toolkit
  5. Writing Business Logic

We began to write business code, blog articles will haveLabelThe concept of,

Define interface

This section is the logic for writing labels. Let’s think about it. General interfaces are based on adding, deleting, modifying and checking. Then let’s define the interfaces.

  • Get tag list: GET(“/tags “)
  • New tag: POST(“/tags “)
  • Update specified tag: PUT(“/tags/:id “)
  • Delete the specified tag: DELETE(“/tags/:id “)

Writing a routing shell

Start writing routing file logic, inroutersNew belowapiDirectory, we are currently the first major API version, so inapiNew belowv1Directory, and then create a new onetag.goFile, write content:

package v1

import (
    "github.com/gin-gonic/gin"
)

//获取多个文章标签
func GetTags(c *gin.Context) {
}

//新增文章标签
func AddTag(c *gin.Context) {
}

//修改文章标签
func EditTag(c *gin.Context) {
}

//删除文章标签
func DeleteTag(c *gin.Context) {
}

Register route

We open itroutersinferiorrouter.goFile, modify the file content as follows:

package routers

import (
    "github.com/gin-gonic/gin"
    
    "gin-blog/routers/api/v1"
    "gin-blog/pkg/setting"
)

func InitRouter() *gin.Engine {
    r := gin.New()

    r.Use(gin.Logger())

    r.Use(gin.Recovery())

    gin.SetMode(setting.RunMode)

    apiv1 := r.Group("/api/v1")
    {
        //获取标签列表
        apiv1.GET("/tags", v1.GetTags)
        //新建标签
        apiv1.POST("/tags", v1.AddTag)
        //更新指定标签
        apiv1.PUT("/tags/:id", v1.EditTag)
        //删除指定标签
        apiv1.DELETE("/tags/:id", v1.DeleteTag)
    }

    return r
}

Current directory structure:

gin-blog/
├── conf
│   └── app.ini
├── main.go
├── middleware
├── models
│   └── models.go
├── pkg
│   ├── e
│   │   ├── code.go
│   │   └── msg.go
│   ├── setting
│   │   └── setting.go
│   └── util
│       └── pagination.go
├── routers
│   ├── api
│   │   └── v1
│   │       └── tag.go
│   └── router.go
├── runtime

Verify that the route was successfully registered

Go back to the command line and executego run main.go, check whether the routing rule was successfully registered.

$ go run main.go 
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:    export GIN_MODE=release
 - using code:    gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /api/v1/tags              --> gin-blog/routers/api/v1.GetTags (3 handlers)
[GIN-debug] POST   /api/v1/tags              --> gin-blog/routers/api/v1.AddTag (3 handlers)
[GIN-debug] PUT    /api/v1/tags/:id          --> gin-blog/routers/api/v1.EditTag (3 handlers)
[GIN-debug] DELETE /api/v1/tags/:id          --> gin-blog/routers/api/v1.DeleteTag (3 handlers)

If the operation is successful, then we are happyStart writing our interface!

Download dependency package


First of all, we have to pullvalidationThe dependency package of will be used for form validation in the following interface.

go get -u github.com/astaxie/beego/validation

Models logic for writing tag lists

CreatemodelsUnder the directorytag.go, write file content:

package models

type Tag struct {
    Model

    Name string `json:"name"`
    CreatedBy string `json:"created_by"`
    ModifiedBy string `json:"modified_by"`
    State int `json:"state"`
}

func GetTags(pageNum int, pageSize int, maps interface {}) (tags []Tag) {
    db.Where(maps).Offset(pageNum).Limit(pageSize).Find(&tags)
    
    return
}

func GetTagTotal(maps interface {}) (count int){
    db.Model(&Tag{}).Where(maps).Count(&count)

    return
}
  1. We created aTag struct{}ForGormThe use of. And give the subsidiary attributejson, like this inc.JSONIt is very convenient to convert the format automatically
  2. Some beginners may see itreturn, and not followed by variables, will not understand; In fact, you can see at the end of the function that we have already declared the return value. This variable can also be used directly in the function body because it was declared at the beginning.
  3. Some people may wonderdbWhere did you come from? Because in the samemodelsUnder the bag, thereforedb *gorm.DBCan be directly used

Routing logic for writing label lists

OpenroutersThe v1 version of thetag.goFirst, let’s write it firstGets the interface for the label list

Modify file contents:

package v1

import (
    "net/http"

    "github.com/gin-gonic/gin"
    //"github.com/astaxie/beego/validation"
    "github.com/Unknwon/com"

    "gin-blog/pkg/e"
    "gin-blog/models"
    "gin-blog/pkg/util"
    "gin-blog/pkg/setting"
)

//获取多个文章标签
func GetTags(c *gin.Context) {
    name := c.Query("name")

    maps := make(map[string]interface{})
    data := make(map[string]interface{})

    if name != "" {
        maps["name"] = name
    }

    var state int = -1
    if arg := c.Query("state"); arg != "" {
        state = com.StrTo(arg).MustInt()
        maps["state"] = state
    }

    code := e.SUCCESS

    data["lists"] = models.GetTags(util.GetPage(c), setting.PageSize, maps)
    data["total"] = models.GetTagTotal(maps)

    c.JSON(http.StatusOK, gin.H{
        "code" : code,
        "msg" : e.GetMsg(code),
        "data" : data,
    })
}

//新增文章标签
func AddTag(c *gin.Context) {
}

//修改文章标签
func EditTag(c *gin.Context) {
}

//删除文章标签
func DeleteTag(c *gin.Context) {
}
  1. c.QueryAvailable for obtaining? name=test&state=1This type of URL parameter, whilec.DefaultQuerySetting a default value is supported
  2. codeVariables are usedeThe error code of the module, which is the previously planned error code, is convenient to debug and identify records.
  3. util.GetPageEnsure that the interface of thepageThe treatment is consistent.
  4. c *gin.ContextYesGinAn important component, which can be understood as context, allows us to pass variables, manage flows, validate JSON for requests, and render JSON responses between intermediates

Run locallycurl 127.0.0.1:8000/api/v1/tags, the correct return value is{"code":200,"data":{"lists":[],"total":0},"msg":"ok"}If there is any problem, please combine the gin results for wrong shooting.

In the interface for obtaining the tag list, we can use thenamestatepageTo filter the query criteria, paging step size can be throughapp.iniConfigure toliststotalThe combination of return to achieve paging effect.

Writing models Logic for Adding Labels

Next we writeAdd tagInterface of

OpenmodelsThe v1 version of thetag.go, modify the file (add 2 methods):

...
func ExistTagByName(name string) bool {
    var tag Tag
    db.Select("id").Where("name = ?", name).First(&tag)
    if tag.ID > 0 {
        return true
    }

    return false
}

func AddTag(name string, state int, createdBy string) bool{
    db.Create(&Tag {
        Name : name,
        State : state,
        CreatedBy : createdBy,
    })

    return true
}
...

Write routing logic for adding tags

OpenroutersUnder the directorytag.go, modify the file (change AddTag method):

package v1

import (
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/astaxie/beego/validation"
    "github.com/Unknwon/com"

    "gin-blog/pkg/e"
    "gin-blog/models"
    "gin-blog/pkg/util"
    "gin-blog/pkg/setting"
)
...
//新增文章标签
func AddTag(c *gin.Context) {
    name := c.Query("name")
    state := com.StrTo(c.DefaultQuery("state", "0")).MustInt()
    createdBy := c.Query("created_by")

    valid := validation.Validation{}
    valid.Required(name, "name").Message("名称不能为空")
    valid.MaxSize(name, 100, "name").Message("名称最长为100字符")
    valid.Required(createdBy, "created_by").Message("创建人不能为空")
    valid.MaxSize(createdBy, 100, "created_by").Message("创建人最长为100字符")
    valid.Range(state, 0, 1, "state").Message("状态只允许0或1")

    code := e.INVALID_PARAMS
    if ! valid.HasErrors() {
        if ! models.ExistTagByName(name) {
            code = e.SUCCESS
            models.AddTag(name, state, createdBy)
        } else {
            code = e.ERROR_EXIST_TAG
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code" : code,
        "msg" : e.GetMsg(code),
        "data" : make(map[string]string),
    })
}
...

UsePostmanPOST accesshttp://127.0.0.1:8000/api/v1/tags? name=1&state=1&created_by=test, viewcodeDo you want to return200Andblog_tagWhether there is a value in the table, the value is correct.

Writing models callbacks

But this time you will find that I clearly added the label, butcreated_onThere is no value at all. When modifying the labelmodified_onWill this problem also exist?

In order to solve this problem, we need to open itmodelsUnder the directorytag.goFile, modify file content (modify package reference and add 2 methods):

package models

import (
    "time"

    "github.com/jinzhu/gorm"
)

...

func (tag *Tag) BeforeCreate(scope *gorm.Scope) error {
    scope.SetColumn("CreatedOn", time.Now().Unix())

    return nil
}

func (tag *Tag) BeforeUpdate(scope *gorm.Scope) error {
    scope.SetColumn("ModifiedOn", time.Now().Unix())

    return nil
}

Restart the service and use it again.PostmanPOST accesshttp://127.0.0.1:8000/api/v1/tags? name=2&state=1&created_by=test, found thatcreated_onIt’s already worth it!

In these sections of code, knowledge points are involved:

This belongs togormTheCallbacks, the callback method can be defined as a pointer to the model structure and will be called when creating, updating, querying and deleting. If any callback returns an error, gorm will stop future operations and roll back all changes.

gormSupported callback methods:

  • Create: foresave, forecreate, AfterCreate, AfterSave
  • Update: foresave, foreupdate, AfterUpdate, AfterSave
  • Delete: BeforeDelete, AfterDelete
  • Query: AfterFind

Write routing logic for the remaining interfaces

Next, let’s finish the remaining two interfaces (EditTag, DeleteTag) at one go.

OpenroutersThe v1 version of thetag.goFile, modify content:

...
//修改文章标签
func EditTag(c *gin.Context) {
    id := com.StrTo(c.Param("id")).MustInt()
    name := c.Query("name")
    modifiedBy := c.Query("modified_by")

    valid := validation.Validation{}

    var state int = -1
    if arg := c.Query("state"); arg != "" {
        state = com.StrTo(arg).MustInt()
        valid.Range(state, 0, 1, "state").Message("状态只允许0或1")
    }

    valid.Required(id, "id").Message("ID不能为空")
    valid.Required(modifiedBy, "modified_by").Message("修改人不能为空")
    valid.MaxSize(modifiedBy, 100, "modified_by").Message("修改人最长为100字符")
    valid.MaxSize(name, 100, "name").Message("名称最长为100字符")

    code := e.INVALID_PARAMS
    if ! valid.HasErrors() {
        code = e.SUCCESS
        if models.ExistTagByID(id) {
            data := make(map[string]interface{})
            data["modified_by"] = modifiedBy
            if name != "" {
                data["name"] = name
            }
            if state != -1 {
                data["state"] = state
            }

            models.EditTag(id, data)
        } else {
            code = e.ERROR_NOT_EXIST_TAG
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code" : code,
        "msg" : e.GetMsg(code),
        "data" : make(map[string]string),
    })
}    

//删除文章标签
func DeleteTag(c *gin.Context) {
    id := com.StrTo(c.Param("id")).MustInt()

    valid := validation.Validation{}
    valid.Min(id, 1, "id").Message("ID必须大于0")

    code := e.INVALID_PARAMS
    if ! valid.HasErrors() {
        code = e.SUCCESS
        if models.ExistTagByID(id) {
            models.DeleteTag(id)
        } else {
            code = e.ERROR_NOT_EXIST_TAG
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code" : code,
        "msg" : e.GetMsg(code),
        "data" : make(map[string]string),
    })
}

Writing models Logic for Other Interfaces

Openmodelsinferiortag.go, modify the file content:

...

func ExistTagByID(id int) bool {
    var tag Tag
    db.Select("id").Where("id = ?", id).First(&tag)
    if tag.ID > 0 {
        return true
    }

    return false
}

func DeleteTag(id int) bool {
    db.Where("id = ?", id).Delete(&Tag{})

    return true
}

func EditTag(id int, data interface {}) bool {
    db.Model(&Tag{}).Where("id = ?", id).Updates(data)

    return true
}
...

Verification function

Restart the service and use Postman.

  • PUT accesshttp://127.0.0.1:8000/api/v1/tags/1? Name = edit1 & state = 0 & modified _ by = edit1 to see if code returns 200
  • DELETE accesshttp://127.0.0.1: 8000/api/v1/tags/1 to see if code returns 200

At this point, Tag’s API’s is complete, and in the next section we will begin to write Article’s API’s!

References

This series of sample codes

This series of catalogues