衔接上文,继续开发。
定义接口
这里我们实现标签相关的逻辑,包括新建标签、获取标签、更新标签、删除标签。
请求接口如下:
- 获取标签列表:GET(“/tags”)
- 新建标签:POST(“/tags”)
- 更新指定标签:PUT(“/tags/:id”)
- 删除指定标签:DELETE(“/tags/:id”)
编写空路由
在routers目录下新建api目录,然后在api下新建v1目录,再新建tag.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 |
package v1 import ( "github.com/gin-gonic/gin" ) //GetTags 获取多个文章标签 func GetTags(c *gin.Context) { } //AddTag 新增文章标签 func AddTag(c *gin.Context) { } //EditTag 修改文章标签 func EditTag(c *gin.Context) { } //DeleteTag 删除文章标签 func DeleteTag(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 |
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) } return r } |
检验路由是否注册成功
执行 go run main.go,就可以检查路由规则是否注册成功。
1 2 3 4 5 6 7 8 9 |
[xiong@AMDServer ginBlog]$ 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 --> ginBlog/routers/api/v1.GetTags (3 handlers) [GIN-debug] POST /api/v1/tags --> ginBlog/routers/api/v1.AddTag (3 handlers) [GIN-debug] PUT /api/v1/tags/:id --> ginBlog/routers/api/v1.EditTag (3 handlers) [GIN-debug] DELETE /api/v1/tags/:id --> ginBlog/routers/api/v1.DeleteTag (3 handlers) |
获取标签
获取标签的Models逻辑
在models目录下创建tag.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 |
package models import ( "gorm.io/gorm" ) //Tag 标签 type Tag struct { gorm.Model Name string `json:"name"` CreatedBy string `json:"created_by"` ModifiedBy string `json:"modified_by"` State int `json:"state"` } //GetTags 获取标签 func GetTags( pageNum int, pageSize int, maps interface{}, ) (tags []Tag) { db.Where(maps).Offset(pageNum).Limit(pageSize).Find(&tags) return } //GetTagTotal 获取标签总数 func GetTagTotal( maps interface{}, ) (count int64) { db.Model(&Tag{}).Where(maps).Count(&count) return } |
获取标签的路由逻辑
继续修改之前的 routers/api/vi 下的tag.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 |
package v1 import ( "ginBlog/models" "ginBlog/pkg/err" "ginBlog/pkg/setting" "ginBlog/pkg/util" "net/http" "github.com/gin-gonic/gin" "github.com/unknwon/com" ) //GetTags 获取多个文章标签 func GetTags(c *gin.Context) { maps := make(map[string]interface{}) data := make(map[string]interface{}) name := c.Query("name") if name != "" { maps["name"] = name } var state int = -1 if arg := c.Query("state"); arg != "" { state = com.StrTo(arg).MustInt() maps["state"] = state } code := err.Success data["lists"] = models.GetTags(util.GetPage(c), setting.GetAPPPageSize(), maps) data["total"] = models.GetTagTotal(maps) c.JSON(http.StatusOK, gin.H{ "code": code, "msg": err.GetMsg(code), "data": data, }) } //AddTag 新增文章标签 func AddTag(c *gin.Context) { } //EditTag 修改文章标签 func EditTag(c *gin.Context) { } //DeleteTag 删除文章标签 func DeleteTag(c *gin.Context) { } |
- c.Query 可以用于获取Url参数,比如 ?name=test&state=1,而 c.DefaultQuery则支持设置一个默认值。
- util.GetPage保证接口对page的处理是一致的
先运行 go run main.go 启动服务,然后请求 192.168.1.101/api/v1/tags,查看返回结果。
{“code”:200,”data”:{“lists”:null,”total”:0},”msg”:”ok”}
因为数据库内没有数据,所以返回的值是空。
新增标签
新增标签的models逻辑
我们先修改models目录下的tag.go文件,新增两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//IsExistTag 判断标签是否存在 // ret: bool 如果标签存在返回true, 否则返回false func IsExistTag(name string) bool { var tag Tag db.Select("id").Where("name = ?", name).First(&tag) return (tag.ID > 0) } //AddTag 新增标签 func AddTag( name string, state int, createdBy string, ) bool { db.Create(&Tag{ Name: name, State: state, CreatedBy: createdBy, }) return true } |
新增标签的路由逻辑
在此之前我们需要获取beego的依赖包validation用于表单验证。
go get -u github.com/astaxie/beego
然后修改router/api/v1目录下的tag.go文件,给我们之前定义的AddTag新增实现。
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 |
//AddTag 新增文章标签 func AddTag(c *gin.Context) { tagName := c.Query("name") state := com.StrTo(c.DefaultQuery("state", "0")).MustInt() createdBy := c.Query("created_by") valid := validation.Validation{} valid.Required(tagName, "name").Message("名字不能为空") valid.MaxSize(tagName, 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 := err.InvalidParams if !valid.HasErrors() { if models.IsExistTag(tagName) { code = err.Success models.AddTag(tagName, state, createdBy) } else { code = err.ErrorTagExist } } else { for _, err := range valid.Errors { log.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": err.GetMsg(code), "data": make(map[string]string), }) } |
然后使用POST访问 192.168.1.101:8000/api/v1/tags?name=1&state=1&created_by=root 查看code返回及数据库表内容。
这里推荐一个工具Postman。
使用教程可以参考一下这里。
然后请求完毕后,可以看一下数据库是否有新增内容。
我这里出了点问题,ORM和数据库之间的衔接出问题了。
1 2 3 4 5 6 7 8 9 |
2020/09/02 12:22:03 /home/xiong/Code/ginBlog/models/tag.go:39 Error 1054: Unknown column 'blog_tag.deleted_at' in 'where clause' [0.768ms] [rows:0] SELECT `id` FROM `blog_tag` WHERE name = "1" AND `blog_tag`.`deleted_at` IS NULL ORDER BY `blog_tag`.`id` LIMIT 1 2020/09/02 12:22:03 /home/xiong/Code/ginBlog/models/tag.go:49 Error 1054: Unknown column 'created_at' in 'field list' [1.364ms] [rows:0] INSERT INTO `blog_tag` (`created_at`,`updated_at`,`deleted_at`,`name`,`created_by`,`modified_by`,`state`) VALUES ("2020-09-02 12:22:03.724","2020-09-02 12:22:03.724",NULL,"1","root","",1) [GIN] 2020/09/02 - 12:22:03 | 200 | 2.486233ms | 192.168.1.102 | POST "/api/v1/tags?name=1&state=1&created_by=root" 2020/09/02 12:22:35 /home/xiong/Code/ginBlog/models/tag.go:23 Error 1054: Unknown column 'blog_tag.deleted_at' in 'where clause' [1.039ms] [rows:0] SELECT * FROM `blog_tag` WHERE `blog_tag`.`deleted_at` IS NULL LIMIT 10 |
需要修改Models,修改models文件夹下models.go文件,新增结构体:
1 2 3 4 5 6 |
//Model ... type Model struct { ID uint `gorm:"primarykey" json:"id"` CreatedOn int `json:"created_on"` ModifiedOn int `json:"modified_on"` } |
然后修改models文件夹下tag.go文件,修改Tag结构体的继承:
1 2 3 4 5 6 7 8 9 |
//Tag 标签 type Tag struct { Model Name string `json:"name"` CreatedBy string `json:"created_by"` ModifiedBy string `json:"modified_by"` State int `json:"state"` } |
然后重新运行服务,发送POST请求,然后查看数据库就有一条新数据了。
但是存在问题是 created_on 和 modified_on 两个字段都没有值。
编写Models Hook
这里我们需要新增两个方法,修改models/tag.go文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//AfterCreate 新建数据Hook func (tag *Tag) AfterCreate(tx *gorm.DB) error { fmt.Println("[models tag AfterCreate] update CreatedOn") tx.Model(tag).UpdateColumn("CreatedOn", time.Now().Unix()) return nil } //AfterUpdate 更新数据Hook func (tag *Tag) AfterUpdate(tx *gorm.DB) error { fmt.Println("[models tag AfterUpdate] update ModifiedOn") tx.Model(tag).UpdateColumn("ModifiedOn", time.Now().Unix()) return nil } |
然后我们再调用一次Post方法,这次把name改为2,整个请求:
192.168.1.101:8000/api/v1/tags?name=2&state=1&created_by=root
然后查看数据库内容即可。
P.S. 这里gorm升级之后,操作方法也变了,参考:
https://github.com/go-gorm/gorm/blob/master/tests/hooks_test.go#L29
编写其他接口逻辑
编写接口Models逻辑
修改models文件夹下tag.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 |
//IsExistTagByName 判断标签是否存在 // name tag.Name string 标签名称 // ret: bool 如果标签存在返回true, 否则返回false func IsExistTagByName(name string) bool { var tag Tag db.Select("id").Where("name = ?", name).First(&tag) return (tag.ID > 0) } //IsExistTagByID 判断标签是否存在 // id tag.ID int 标签ID // ret: bool 如果标签存在返回true,否则返回false func IsExistTagByID(id int) bool { var tag Tag db.Select("id").Where("id = ?", id).First(&tag) return tag.ID > 0 } //AddTag 新增标签 func AddTag( name string, state int, createdBy string, ) bool { db.Create(&Tag{ Name: name, State: state, CreatedBy: createdBy, }) return true } //EditTag 修改标签 func EditTag( id int, data interface{}, ) bool { db.Model(&Tag{}).Where("id = ?", id).Updates(data) return true } //DeleteTag 删除标签 func DeleteTag( id int, ) bool { db.Where("id = ?", id).Delete(&Tag{}) return true } |
新增了EditTag 和 DeleteTag方法,修改IsExistTag方法,拓展为IsExistTagByName和IsExistTagByID。
编写接口路由逻辑
这里修改 routers/api/v1/tag.go 文件,补全剩下的EditTag、DeleteTag方法。
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 |
//EditTag 修改文章标签 func EditTag(c *gin.Context) { id := com.StrTo(c.Param("id")).MustInt() name := c.Query("name") modifiedBy := c.Query("modified_by") valid := validation.Validation{} valid.Required(name, "name").Message("名字不能为空") valid.MaxSize(name, 100, "name").Message("名称最长为100字符") //这个长度跟随数据库字段长度 valid.Required(modifiedBy, "modified_by").Message("修改人不能为空") valid.MaxSize(modifiedBy, 100, "modified_by").Message("修改人最长为100字符") //state 这个字段可有可无,如果不存在则默认-1,存在则需要校验 var state int = -1 if arg := c.Query("state"); arg != "" { state = com.StrTo(arg).MustInt() valid.Range(state, 0, 1, "state").Message("状态只允许0或者1") } code := err.InvalidParams if !valid.HasErrors() { code = err.Success if models.IsExistTagByID(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 = err.ErrorTagNotExist } } else { for _, err := range valid.Errors { log.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": err.GetMsg(code), "data": make(map[string]string), }) } //DeleteTag 删除文章标签 func DeleteTag(c *gin.Context) { id := com.StrTo(c.Param("id")).MustInt() valid := validation.Validation{} valid.Min(id, 1, "id").Message("ID必须大于0") code := err.InvalidParams if !valid.HasErrors() { code = err.Success if models.IsExistTagByID(id) { models.DeleteTag(id) } else { code = err.ErrorTagNotExist } } else { for _, err := range valid.Errors { log.Info(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": err.GetMsg(code), "data": make(map[string]string), }) } |
然后重启服务,使用Postman工具调用修改和删除标签。
PUT 修改标签:
192.168.1.101:8000/api/v1/tags/1?name=edit1&state=0&modified_by=root
DELETE 删除标签:
192.168.1.101:8000/api/v1/tags/1
查看code返回值是否为200。
Delete没问题,PUT这里回调AfterUpdate更新modified_on一直失败。
日志查询是没有指定id,所以需要修改两处:
1. 数据修改Hook这里两个函数更新了一下
1 2 3 4 5 6 7 8 9 |
//AfterCreate 新建数据Hook func (tag *Tag) AfterCreate(tx *gorm.DB) error { return tx.Model(tag).UpdateColumn("CreatedOn", time.Now().Unix()).Error } //AfterUpdate 更新数据Hook func (tag *Tag) AfterUpdate(tx *gorm.DB) error { return tx.Model(tag).Where("id = ?", tag.ID).UpdateColumn("ModifiedOn", time.Now().Unix()).Error } |
2. 路由routers/api/v1/tag.go文件,EditTag函数修改data,往里面多写一个id变量:
1 2 3 4 5 6 7 8 9 10 |
data := make(map[string]interface{}) data["id"] = id data["modified_by"] = modifiedBy if name != "" { data["name"] = name } if state != -1 { data["state"] = state } models.EditTag(id, data) |
然后重启服务,再测试,OK了…