资讯专栏INFORMATION COLUMN

Gin实践 连载四 搭建Blog API's(三)

heartFollower / 3188人阅读

摘要:编写的项目地址定义接口本节编写文章的逻辑,我们定义一下接口吧获取文章列表获取指定文章新建文章更新指定文章删除指定文章编写路由逻辑在的版本下,新建文件,写入内容获取单个文章获取多个文章新增文章修改文章删除文章我们打开下的文件,修改文件内容为

编写Article的API"s、Models

项目地址:https://github.com/EDDYCJY/go...

定义接口

本节编写文章的逻辑,我们定义一下接口吧!

获取文章列表:GET("/articles")

获取指定文章:POST("/articles/:id")

新建文章:POST("/articles")

更新指定文章:PUT("/articles/:id")

删除指定文章:DELETE("/articles/:id")

编写路由逻辑

routers的v1版本下,新建article.go文件,写入内容:

package v1

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

//获取单个文章
func GetArticle(c *gin.Context) {
}

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

//新增文章
func AddArticle(c *gin.Context) {
}

//修改文章
func EditArticle(c *gin.Context) {
}

//删除文章
func DeleteArticle(c *gin.Context) {
}

我们打开routers下的router.go文件,修改文件内容为:

package routers

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

func InitRouter() *gin.Engine {
    ...
    apiv1 := r.Group("/api/v1")
    {
        ...
        //获取文章列表
        apiv1.GET("/articles", v1.GetArticles)
        //获取指定文章
        apiv1.GET("/articles/:id", v1.GetArticle)
        //新建文章
        apiv1.POST("/articles", v1.AddArticle)
        //更新指定文章
        apiv1.PUT("/articles/:id", v1.EditArticle)
        //删除指定文章
        apiv1.DELETE("/articles/:id", v1.DeleteArticle)
    }

    return r
}

当前目录结构:

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

在基础的路由规则配置结束后,我们开始编写我们的接口吧!


编写models逻辑

创建models目录下的article.go,写入文件内容:

package models

import (
    "github.com/jinzhu/gorm"

    "time"
)

type Article struct {
    Model

    TagID int `json:"tag_id" gorm:"index"`
    Tag   Tag `json:"tag"`

    Title string `json:"title"`
    Desc string `json:"desc"`
    Content string `json:"content"`
    CreatedBy string `json:"created_by"`
    ModifiedBy string `json:"modified_by"`
    State int `json:"state"`
}


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

    return nil
}

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

    return nil
}

我们创建了一个Article struct {},与Tag不同的是,Article多了几项

gorm:index,用于声明这个字段为索引,如果你使用了自动迁移功能则会有所影响,在不使用则无影响

Tag字段,实际是一个嵌套的struct,它利用TagIDTag模型相互关联,在执行查询的时候,能够达到ArticleTag关联查询的功能

time.Now().Unix() 返回当前的时间戳

接下来,请确保已对上一章节的内容通读且了解,由于逻辑偏差不会太远,我们本节直接编写这五个接口


打开models目录下的article.go,修改文件内容:

package models

import (
    "time"

    "github.com/jinzhu/gorm"
)

type Article struct {
    Model

    TagID int `json:"tag_id" gorm:"index"`
    Tag   Tag `json:"tag"`

    Title string `json:"title"`
    Desc string `json:"desc"`
    Content string `json:"content"`
    CreatedBy string `json:"created_by"`
    ModifiedBy string `json:"modified_by"`
    State int `json:"state"`
}


func ExistArticleByID(id int) bool {
    var article Article
    db.Select("id").Where("id = ?", id).First(&article)

    if article.ID > 0 {
        return true
    }

    return false
}

func GetArticleTotal(maps interface {}) (count int){
    db.Model(&Article{}).Where(maps).Count(&count)

    return
}

func GetArticles(pageNum int, pageSize int, maps interface {}) (articles []Article) {
    db.Preload("Tag").Where(maps).Offset(pageNum).Limit(pageSize).Find(&articles)

    return
}

func GetArticle(id int) (article Article) {
    db.Where("id = ?", id).First(&article)
    db.Model(&article).Related(&article.Tag)

    return 
}

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

    return true
}

func AddArticle(data map[string]interface {}) bool {
    db.Create(&Article {
        TagID : data["tag_id"].(int),
        Title : data["title"].(string),
        Desc : data["desc"].(string),
        Content : data["content"].(string),
        CreatedBy : data["created_by"].(string),
        State : data["state"].(int),
    })

    return true
}

func DeleteArticle(id int) bool {
    db.Where("id = ?", id).Delete(Article{})

    return true
}

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

    return nil
}

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

    return nil
}

在这里,我们拿出三点不同来讲

1、 我们的Article是如何关联到Tag???

func GetArticle(id int) (article Article) {
    db.Where("id = ?", id).First(&article)
    db.Model(&article).Related(&article.Tag)

    return 
}

能够达到关联,首先是gorm本身做了大量的约定俗成

Article有一个结构体成员是TagID,就是外键。gorm会通过类名+ID的方式去找到这两个类之间的关联关系

Article有一个结构体成员是Tag,就是我们嵌套在Article里的Tag结构体,我们可以通过Related进行关联查询

2、 Preload是什么东西,为什么查询可以得出每一项的关联Tag

func GetArticles(pageNum int, pageSize int, maps interface {}) (articles []Article) {
    db.Preload("Tag").Where(maps).Offset(pageNum).Limit(pageSize).Find(&articles)

    return
}

Preload就是一个预加载器,它会执行两条SQL,分别是SELECT * FROM blog_articles;SELECT * FROM blog_tag WHERE id IN (1,2,3,4);,那么在查询出结构后,gorm内部处理对应的映射逻辑,将其填充到ArticleTag中,会特别方便,并且避免了循环查询

那么有没有别的办法呢,大致是两种

gormJoin

循环Related

综合之下,还是Preload更好,如果你有更优的方案,欢迎说一下 :)

3、 v.(I) 是什么?

v表示一个接口值,I表示接口类型。这个实际就是Golang中的类型断言,用于判断一个接口值的实际类型是否为某个类型,或一个非接口值的类型是否实现了某个接口类型


打开routers目录下v1版本的article.go文件,修改文件内容:

package v1

import (
    "net/http"
    "log"

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

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

//获取单个文章
func GetArticle(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
    var data interface {}
    if ! valid.HasErrors() {
        if models.ExistArticleByID(id) {
            data = models.GetArticle(id)
            code = e.SUCCESS
        } else {
            code = e.ERROR_NOT_EXIST_ARTICLE
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

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

//获取多个文章
func GetArticles(c *gin.Context) {
    data := make(map[string]interface{})
    maps := make(map[string]interface{})
    valid := validation.Validation{}

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

        valid.Range(state, 0, 1, "state").Message("状态只允许0或1")
    }

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

        valid.Min(tagId, 1, "tag_id").Message("标签ID必须大于0")
    } 

    code := e.INVALID_PARAMS
    if ! valid.HasErrors() {
        code = e.SUCCESS

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

    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

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

//新增文章
func AddArticle(c *gin.Context) {
    tagId := com.StrTo(c.Query("tag_id")).MustInt()
    title := c.Query("title")
    desc := c.Query("desc")
    content := c.Query("content")
    createdBy := c.Query("created_by")
    state := com.StrTo(c.DefaultQuery("state", "0")).MustInt()

    valid := validation.Validation{}
    valid.Min(tagId, 1, "tag_id").Message("标签ID必须大于0")
    valid.Required(title, "title").Message("标题不能为空")
    valid.Required(desc, "desc").Message("简述不能为空")
    valid.Required(content, "content").Message("内容不能为空")
    valid.Required(createdBy, "created_by").Message("创建人不能为空")
    valid.Range(state, 0, 1, "state").Message("状态只允许0或1")

    code := e.INVALID_PARAMS
    if ! valid.HasErrors() {
        if models.ExistTagByID(tagId) {
            data := make(map[string]interface {})
            data["tag_id"] = tagId
            data["title"] = title
            data["desc"] = desc
            data["content"] = content
            data["created_by"] = createdBy
            data["state"] = state
            
            models.AddArticle(data)
            code = e.SUCCESS
        } else {
            code = e.ERROR_NOT_EXIST_TAG
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

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

//修改文章
func EditArticle(c *gin.Context) {
    valid := validation.Validation{}

    id := com.StrTo(c.Param("id")).MustInt()
    tagId := com.StrTo(c.Query("tag_id")).MustInt()
    title := c.Query("title")
    desc := c.Query("desc")
    content := c.Query("content")
    modifiedBy := c.Query("modified_by")

    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.Min(id, 1, "id").Message("ID必须大于0")
    valid.MaxSize(title, 100, "title").Message("标题最长为100字符")
    valid.MaxSize(desc, 255, "desc").Message("简述最长为255字符")
    valid.MaxSize(content, 65535, "content").Message("内容最长为65535字符")
    valid.Required(modifiedBy, "modified_by").Message("修改人不能为空")
    valid.MaxSize(modifiedBy, 100, "modified_by").Message("修改人最长为100字符")

    code := e.INVALID_PARAMS
    if ! valid.HasErrors() {
        if models.ExistArticleByID(id) {
            if models.ExistTagByID(tagId) {
                data := make(map[string]interface {})
                if tagId > 0 {
                    data["tag_id"] = tagId
                }
                if title != "" {
                    data["title"] = title
                }
                if desc != "" {
                    data["desc"] = desc
                }
                if content != "" {
                    data["content"] = content
                }

                data["modified_by"] = modifiedBy

                models.EditArticle(id, data)
                code = e.SUCCESS
            } else {
                code = e.ERROR_NOT_EXIST_TAG
            }
        } else {
            code = e.ERROR_NOT_EXIST_ARTICLE
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

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

//删除文章
func DeleteArticle(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() {
        if models.ExistArticleByID(id) {
            models.DeleteArticle(id)
            code = e.SUCCESS
        } else {
            code = e.ERROR_NOT_EXIST_ARTICLE
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

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

当前目录结构:

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

我们重启服务,执行go run main.go,检查控制台输出结果

$ 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)
[GIN-debug] GET    /api/v1/articles          --> gin-blog/routers/api/v1.GetArticles (3 handlers)
[GIN-debug] GET    /api/v1/articles/:id      --> gin-blog/routers/api/v1.GetArticle (3 handlers)
[GIN-debug] POST   /api/v1/articles          --> gin-blog/routers/api/v1.AddArticle (3 handlers)
[GIN-debug] PUT    /api/v1/articles/:id      --> gin-blog/routers/api/v1.EditArticle (3 handlers)
[GIN-debug] DELETE /api/v1/articles/:id      --> gin-blog/routers/api/v1.DeleteArticle (3 handlers)

使用Postman检验接口是否正常(大家可以选用合适的参数传递方式,此处为了方便展示我选用了URL传参),

POST:http://127.0.0.1:8000/api/v1/articles?tag_id=1&title=test1&desc=test-desc&content=test-content&created_by=test-created&state=1

GET:http://127.0.0.1:8000/api/v1/articles

GET:http://127.0.0.1:8000/api/v1/articles/1

PUT:http://127.0.0.1:8000/api/v1/articles/1?tag_id=1&title=test-edit1&desc=test-desc-edit&content=test-content-edit&modified_by=test-created-edit&state=0

DELETE:http://127.0.0.1:8000/api/v1/articles/1

至此,我们的API"s编写就到这里,下一节我们将介绍另外的一些技巧!

参考 本系列示例代码

go-gin-example

本系列目录

连载一 Golang介绍与环境安装

连载二 搭建Blog API"s(一)

连载三 搭建Blog API"s(二)

连载四 搭建Blog API"s(三)

连载五 使用JWT进行身份校验

连载六 编写一个简单的文件日志

连载七 Golang优雅重启HTTP服务

连载八 为它加上Swagger

连载九 将Golang应用部署到Docker

连载十 定制 GORM Callbacks

连载十一 Cron定时任务

连载十二 优化配置结构及实现图片上传

连载十三 优化你的应用结构和实现Redis缓存

连载十四 实现导出、导入 Excel

连载十五 生成二维码、合并海报

连载十六 在图片上绘制文字

连载十七 用 Nginx 部署 Go 应用

番外 Golang交叉编译

番外 请入门 Makefile

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/47560.html

相关文章

  • Gin实践 连载 搭建Blog API's(二)

    摘要:在获取标签列表接口中,我们可以根据来筛选查询条件,分页的步长可通过进行配置,以的组合返回达到分页效果。 编写Tag的APIs、Models 项目地址:https://github.com/EDDYCJY/go... 本大节将会涉及到以下知识点: gin:Golang的一个微框架,性能极佳 beego-validation:本节采用的beego的表单验证库,中文文档 gorm,...

    Ilikewhite 评论0 收藏0
  • Gin实践 连载搭建Blog API's(一)

    摘要:介绍和初始化项目初始工作区首先,我们需要增加一个工作区路径用于我们的项目。将你新的工作区加入到中的环境变量中,并在新工作区中,建立三个目录。 Gin搭建Blog APIs (一) 项目地址:https://github.com/EDDYCJY/go... 思考 首先,在一个初始项目开始前,大家都要思考一下 各种的程序配置写在代码中,好吗 API的错误码硬编在程序中,合适吗 db句柄谁...

    chuyao 评论0 收藏0
  • Gin实践 连载五 使用JWT进行身份校验

    摘要:原文地址使用进行身份校验在前面几节中,我们已经基本的完成了的编写但是,还存在一些非常严重的问题,例如,我们现在的是可以随意调用的,这显然还不够完美,是有问题的那么我们采用的方式来简单解决这个问题项目地址下载依赖包首先,我们下载的依赖包 原文地址:使用JWT进行身份校验 在前面几节中,我们已经基本的完成了APIs的编写 但是,还存在一些非常严重的问题,例如,我们现在的API是可以随意调...

    source 评论0 收藏0
  • Gin实践 连载九 将Golang应用部署到Docker

    摘要:项目地址快上车,支持一波原文地址将应用部署到注开始前你需要安装好,配好镜像源本章节源码在分支上从本章节开始项目目录都以为基准请配合自己本地项目灵活变动介绍在这里简单介绍下,建议深入学习是一个开源的轻量级容器技术,让开发者可以打包他们 项目地址:https://github.com/EDDYCJY/go... (快上车,支持一波)原文地址:将Golang应用部署到Docker 注: 开...

    Hancock_Xu 评论0 收藏0
  • Gin实践 连载九 将Golang应用部署到Docker

    摘要:将应用部署到项目地址快上车,支持一波原文地址注开始前你需要安装好,配好镜像源本章节源码在分支上从本章节开始项目目录都以为基准请配合自己本地项目灵活变动介绍在这里简单介绍下,建议深入学习是一个开源的轻量级容器技术,让开发者可以打包他们 将Golang应用部署到Docker 项目地址:https://github.com/EDDYCJY/go... (快上车,支持一波)原文地址:https...

    Half 评论0 收藏0

发表评论

0条评论

heartFollower

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<