摘要:编写的项目地址定义接口本节编写文章的逻辑,我们定义一下接口吧获取文章列表获取指定文章新建文章更新指定文章删除指定文章编写路由逻辑在的版本下,新建文件,写入内容获取单个文章获取多个文章新增文章修改文章删除文章我们打开下的文件,修改文件内容为
编写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目录下的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,它利用TagID与Tag模型相互关联,在执行查询的时候,能够达到Article、Tag关联查询的功能
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内部处理对应的映射逻辑,将其填充到Article的Tag中,会特别方便,并且避免了循环查询
那么有没有别的办法呢,大致是两种
gorm的Join
循环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
摘要:在获取标签列表接口中,我们可以根据来筛选查询条件,分页的步长可通过进行配置,以的组合返回达到分页效果。 编写Tag的APIs、Models 项目地址:https://github.com/EDDYCJY/go... 本大节将会涉及到以下知识点: gin:Golang的一个微框架,性能极佳 beego-validation:本节采用的beego的表单验证库,中文文档 gorm,...
摘要:介绍和初始化项目初始工作区首先,我们需要增加一个工作区路径用于我们的项目。将你新的工作区加入到中的环境变量中,并在新工作区中,建立三个目录。 Gin搭建Blog APIs (一) 项目地址:https://github.com/EDDYCJY/go... 思考 首先,在一个初始项目开始前,大家都要思考一下 各种的程序配置写在代码中,好吗 API的错误码硬编在程序中,合适吗 db句柄谁...
摘要:原文地址使用进行身份校验在前面几节中,我们已经基本的完成了的编写但是,还存在一些非常严重的问题,例如,我们现在的是可以随意调用的,这显然还不够完美,是有问题的那么我们采用的方式来简单解决这个问题项目地址下载依赖包首先,我们下载的依赖包 原文地址:使用JWT进行身份校验 在前面几节中,我们已经基本的完成了APIs的编写 但是,还存在一些非常严重的问题,例如,我们现在的API是可以随意调...
摘要:项目地址快上车,支持一波原文地址将应用部署到注开始前你需要安装好,配好镜像源本章节源码在分支上从本章节开始项目目录都以为基准请配合自己本地项目灵活变动介绍在这里简单介绍下,建议深入学习是一个开源的轻量级容器技术,让开发者可以打包他们 项目地址:https://github.com/EDDYCJY/go... (快上车,支持一波)原文地址:将Golang应用部署到Docker 注: 开...
摘要:将应用部署到项目地址快上车,支持一波原文地址注开始前你需要安装好,配好镜像源本章节源码在分支上从本章节开始项目目录都以为基准请配合自己本地项目灵活变动介绍在这里简单介绍下,建议深入学习是一个开源的轻量级容器技术,让开发者可以打包他们 将Golang应用部署到Docker 项目地址:https://github.com/EDDYCJY/go... (快上车,支持一波)原文地址:https...
阅读 785·2023-04-25 23:50
阅读 1660·2021-11-19 09:40
阅读 435·2019-08-30 13:50
阅读 2606·2019-08-29 17:11
阅读 937·2019-08-29 16:37
阅读 2868·2019-08-29 12:54
阅读 2670·2019-08-28 18:17
阅读 2510·2019-08-26 16:55