衔接上文,继续开发。
先用tree命令,记录一下完成前面两个部分之后的文件结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
[xiong@AMDServer ginBlog]$ tree . ├── conf │ └── app.ini ├── go.mod ├── go.sum ├── main.go ├── middleware ├── models │ ├── models.go │ └── tag.go ├── pkg │ ├── err │ │ ├── code.go │ │ └── msg.go │ ├── setting │ │ └── setting.go │ └── util │ └── pagination.go ├── routers │ ├── api │ │ └── v1 │ │ └── tag.go │ └── router.go └── runtime 11 directories, 12 files |
定义接口
接下来我们编写文章的逻辑,定义接口如下:
- 获取文章列表:GET(“/articles”)
- 获取指定文章:GET(“/articles/:id”)
- 新建文章:POST(“/articles”)
- 更新指定文章:PUT(“/articles/:id”)
- 删除指定文章:DELETE(“/articles/:id”)
路由接口
在routers/api/v1目录下新建article.go文件,写入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package v1 import ( "github.com/gin-gonic/gin" ) //GetArticle 获取单个文章 func GetArticle(c *gin.Context) { } //GetArticles 获取多个文章 func GetArticles(c *gin.Context) { } //AddArticle 新增文章 func AddArticle(c *gin.Context) { } //EditArticle 修改文章 func EditArticle(c *gin.Context) { } //DeleteArticle 删除文章 func DeleteArticle(c *gin.Context) { } |
然后修改routers下的router.go文件,主要是添加对文章的路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package routers import ( "ginBlog/pkg/setting" v1 "ginBlog/routers/api/v1" "github.com/gin-gonic/gin" ) //InitRouter 初始化路由 func InitRouter() *gin.Engine { r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) gin.SetMode(setting.GetRunMode()) 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) //文章路由 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 } |
Models逻辑
一次性完成所有的接口吧,在models目录下创建article.go文件,写入文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
package models import ( "time" "gorm.io/gorm" ) //Article 文章 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"` } //AfterCreate 新建数据Hook func (article *Article) AfterCreate(tx *gorm.DB) error { return tx.Model(article). UpdateColumn("CreatedOn", time.Now().Unix()).Error } //AfterUpdate 更新数据Hook func (article *Article) AfterUpdate(tx *gorm.DB) error { return tx.Model(article). Where("id = ?", article.ID). UpdateColumn("ModifiedOn", time.Now().Unix()).Error } //IsExistArticleByID 判断文章是否存在 // id article.ID int 文章ID // ret: bool 如果文章存在返回true,否则返回false func IsExistArticleByID(id int) bool { var article Article db.Select("id").Where("id = ?", id).First(&article) return article.ID > 0 } //GetArticleTotal 获取文章总数 func GetArticleTotal( maps interface{}, ) (count int64) { db.Model(&Article{}).Where(maps).Count(&count) return } //GetArticels 获取文章 func GetArticels( pageNum int, pageSize int, maps interface{}, ) (articles []Article) { db.Preload("Tag").Where(maps).Offset(pageNum).Limit(pageSize).Find(&articles) return } //GetArticle 获取文章 func GetArticle(id int) (article Article) { db.Preload("Tag").Where("id = ?", id).First(&article) return } //EditArticle 更新文章 func EditArticle( id int, data interface{}, ) bool { err := db.Model(&Article{}).Where("id = ?", id).Updates(data).Error return err == nil } //AddArticle 新增文章 func AddArticle( data map[string]interface{}, ) bool { err := 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), }).Error return err == nil } //DeleteArticle 删除文章 func DeleteArticle(id int) bool { err := db.Where("id = ?", id).Delete(Article{}).Error return err == nil } |
这里讲两点:
1. Article如何关联Tag的,Preload是什么?
gorm会通过类名+ID的方式去找两个类之间的关联关系。
PreLoad是一个预加载器,它会执行两条SQL:
1 2 |
SELECT * FROM blog_articles; SELECT * FROM blog_tag WHERE id IN (1, 2, 3, 4); |
在查询出结果之后,gorm内部处理对应的映射逻辑,将它填充到Article的Tag中。
2. value.(type) 是什么?
value是一个接口值,type是接口类型。比如 data[“tag_id”].(int)
实际是go语言中的类型断言,判断接口值的实际类型是不是某个类型。
路由逻辑
接下来我们修改 routers/api/v1目录下的article.go文件,修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
package v1 import ( "ginBlog/models" "ginBlog/pkg/e" "ginBlog/pkg/setting" "ginBlog/pkg/util" "net/http" "github.com/astaxie/beego/validation" "github.com/gin-gonic/gin" "github.com/prometheus/common/log" "github.com/unknwon/com" ) //GetArticle 获取单个文章 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.InvalidParams var data interface{} if !valid.HasErrors() { if models.IsExistArticleByID(id) { data = models.GetArticle(id) code = e.Success } else { code = e.ErrorArticleNotExist } } else { for _, err := range valid.Errors { log.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": e.GetMsg(code), "data": data, }) } //GetArticles 获取多个文章 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 = -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.InvalidParams if !valid.HasErrors() { code = e.Success data["lists"] = models.GetArticels(util.GetPage(c), setting.GetAPPPageSize(), maps) data["total"] = models.GetArticleTotal(maps) } else { for _, err := range valid.Errors { log.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": e.GetMsg(code), "data": data, }) } //AddArticle 新增文章 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.InvalidParams if !valid.HasErrors() { if models.IsExistTagByID(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 if models.AddArticle(data) { code = e.Success } else { code = e.Error } } else { code = e.ErrorTagNotExist } } else { for _, err := range valid.Errors { log.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": e.GetMsg(code), "data": make(map[string]interface{}), }) } //EditArticle 修改文章 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.InvalidParams if !valid.HasErrors() { if models.IsExistArticleByID(id) { if models.IsExistTagByID(tagID) { data := make(map[string]interface{}) data["id"] = id 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.ErrorTagNotExist } } else { code = e.ErrorArticleNotExist } } else { for _, err := range valid.Errors { log.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": e.GetMsg(code), "data": make(map[string]string), }) } //DeleteArticle 删除文章 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.InvalidParams if !valid.HasErrors() { if models.IsExistArticleByID(id) { models.DeleteArticle(id) code = e.Success } else { code = e.ErrorArticleNotExist } } else { for _, err := range valid.Errors { log.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": e.GetMsg(code), "data": make(map[string]string), }) } |
这里我把err的包改为e…因为它和常用的变量名err重复了,所以你们把之前的都改改吧,晕…
修改后目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
[xiong@AMDServer ginBlog]$ tree . ├── conf │ └── app.ini ├── go.mod ├── go.sum ├── 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 11 directories, 14 files |
功能验证
请求文章:
GET 192.168.1.101:8000/api/v1/articles
GET 192.168.1.101:8000/api/v1/articles/1
创建文章:
POST 192.168.1.101:8000/api/v1/articles?tag_id=1&title=test1&desc=test-desc&content=test-content&created_by=root&state=1
更新
PUT 192.168.1.101:8000/api/v1/articles/1?tag_id=1&title=test-edit1&desc=test-desc-edit&content=test-content-edit&modified_by=root&state=0
删除
DELETE 192.168.1.101:8000/api/v1/articles/1
这里可以用 Postman 一一验证。
验证结果
发现存在两个问题,
一个是创建文章的时候无效参数,是因为写错了一个判断条件,已修复。
另一个是修改文章的时候,modified_on 字段没有更新,发现是忘记传id进去了。
需要在 routers/api/v1/article.go 文件 EditArticle 函数加上 data[“id”] = id 这段赋值。
已经修复好了。