這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
編寫Article的API's、Models
定義介面
本節編寫文章的邏輯,我們定義一下介面吧!
- 擷取文章列表:GET("/articles")
- 擷取指定文章:POST("/articles/:id")
- 建立文章:POST("/articles")
- 更新指定文章:PUT("/articles/:id")
- 刪除指定文章:DELETE("/articles/:id")
編寫路由邏輯
在routers的v1版本下,建立article.go檔案,寫入內容:
package v1import ( "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 routersimport ( "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 modelsimport ( "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:"title"` 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 modelsimport ( "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:"title"` 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中,會特別方便,並且避免了迴圈查詢
那麼有沒有別的辦法呢,大致是兩種
綜合之下,還是Preload更好,如果你有更優的方案,歡迎說一下 :)
3、 v.(I) 是什嗎?
v表示一個介面值,I表示介面類型。這個實際就是Golang中的類型斷言,用於判斷一個介面值的實際類型是否為某個類型,或一個非介面值的類型是否實現了某個介面類型
開啟routers目錄下v1版本的article.go檔案,修改檔案內容:
package v1import ( "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")).Int() 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.Println(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).Int() 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).Int() 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.Println(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")).Int() title := c.Query("title") desc := c.Query("desc") content := c.Query("content") createdBy := c.Query("created_by") state, _ := com.StrTo(c.DefaultQuery("state", "0")).Int() 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.Println(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")).Int() tagId, _ := com.StrTo(c.Query("tag_id")).Int() 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).Int() 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.Println(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")).Int() 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.Println(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編寫就到這裡,下一節我們將介紹另外的一些技巧!
參考
本系列範例程式碼