# 04. Gin framwork (demo with todolist)
###### tags: `golang練習`###### tags: `golang練習`
## TL;DR:
1. DB query的時候若使用find(返回值非唯一時),記得使用limit()
1. DB 需使用時才開,不要開啟來等
1. 可使用c.Set()傳遞變數
1. 回傳訊息請統一格式
1. Url Scheme, Host, port, path, Key_type等資料請寫進config
1. load config 請不要寫死(可以使用cmd做彈性讀取)
## Set router
```go
func Set_router(configs *tools.Config_data) {
r := gin.New()
r.Use(gin.Recovery())
r.Use(middlewares.Load_configs(configs))
// for k8s livenessprobe
r.GET("/alive", alive)
// funcitons
r.GET("/api", get) // 首頁
r.POST("/api", create) // 建立
r.PATCH("/api", update) // 更新
r.DELETE("/api", delete) // 刪除
err := endless.ListenAndServe(":30003", r)
if err != nil {
log.Error().Caller().Str("func", "endless.ListenAndServe(\":30003\", r)").Err(err).Msg("Web")
return
}
}
```
1. **gin.New() or gin.Default()**
* create a router(engine) to dierect to other path or function.
* Default() include two middlewares, Logger() and Recovery().
* Logger(): write the logs during the server serving.
* Recovery(): recover from any panic and send a 500 code to server if it happened.
1. **r.USE, GET, POST, PATCH, PUT, DELETE (path, func)**
* request methods ask the path react by this function
1. **endless.ListenAndServe**
* to make graceful shutdown(先關閉連接埠,確保沒有新的使用者連上服務,第二步驟就是確保處理完剩下的 http 連線才會正常關閉).
* there is another way to do it. [here](https://chenyitian.gitbooks.io/gin-web-framework/content/docs/38.html)
---
## Create
```go
// Binding from JSON
type New_subject struct {
Subject string `json:"subject" binding:"required,min=1"`
User_id string `json:"user_id"`
}
func create(c *gin.Context) {
var subject New_subject
// 接收前端訊息
if err := c.ShouldBindJSON(&subject); err != nil {
log.Error().Caller().Str("func", "ShouldBindJSON(&subject)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "subject type error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
user_id, err := tools.Parse_int(subject.User_id)
if err != nil {
log.Error().Caller().Str("func", "tools.Parse_int(user_id_raw").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "user_id convert error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 開DB
db_conn, err := models.Db_conn_web(c)
defer models.Db_close(db_conn)
if err != nil {
log.Error().Caller().Str("func", "tools.Db_conn(c)").Err(err).Msg("Db")
if err := tools.Msg_send(c, "error", "db error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 找user
var user models.Account
err = db_conn.Where("Id = ? ", user_id).First(&user).Error
if err != nil {
log.Error().Caller().Str("func", "db_conn.Where(\"id = ?\", user_id))").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db query error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 建立資料並寫入
err = db_conn.Model(&user).Association("Todolists").Append([]models.Todolist{{Subject: subject.Subject, Status: 1}})
if err != nil {
log.Error().Caller().Str("func", "db_conn.Model(&user).Association(\"Todolist\")").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db create error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 成功
if err := tools.Msg_send(c, "success", "subject created", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
return
}
}
```
1. **c.ShouldBindJSON(&struct)**
* authenticate the data from server match in the struct and the tag(binding: ...)
---
## Get
```go
func get(c *gin.Context) {
// 拿取user_id
user_id_raw, b := c.GetQuery("user_id")
if !b {
log.Error().Caller().Str("func", "c.GetQuery(\"user_id\")").Str("msg", "get user id failed").Msg("Web")
if err := tools.Msg_send(c, "error", "get user id failed", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// set user_id for other function
c.Set("user_id", user_id_raw)
user_id, err := tools.Parse_int(user_id_raw)
if err != nil {
log.Error().Caller().Str("func", "tools.Parse_int(user_id_raw)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "user_id convert error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 取得分群
group, b := c.GetQuery("group")
if !b {
log.Error().Caller().Str("func", "c.GetQuery(\"group\")").Str("msg", "get group failed").Msg("Web")
if err := tools.Msg_send(c, "error", "get group failed", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 取得頁面顯示數
slice_target_raw, b := c.GetQuery("slice_target")
if !b {
log.Error().Caller().Str("func", "c.GetQuery(\"slice_target\")").Str("msg", "get slice_target failed").Msg("Web")
if err := tools.Msg_send(c, "error", "get slice_target failed", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
slice_target, err := tools.Parse_int(slice_target_raw)
if err != nil {
log.Error().Caller().Str("func", "tools.Parse_int(slice_target_raw)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "slice_target convert error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 取得當前頁數
page_raw, b := c.GetQuery("page")
if !b {
log.Error().Caller().Str("func", "c.GetQuery(\"page\")").Str("msg", "get page failed").Msg("Web")
if err := tools.Msg_send(c, "error", "get page failed", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
page, err := tools.Parse_int(page_raw)
if err != nil {
log.Error().Caller().Str("func", "tools.Parse_int(page_raw)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "page convert error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 無<1的頁面
if page <= 0 {
if err := tools.Msg_send(c, "error", "no other page", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 開DB
db_conn, err := models.Db_conn_web(c)
defer models.Db_close(db_conn)
if err != nil {
log.Error().Caller().Str("func", "tools.Db_conn(c)").Err(err).Msg("Db")
if err := tools.Msg_send(c, "error", "db error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
var todolists []models.Todolist
// 查詢所有非0(軟刪除的)data
err = db_conn.Order("id desc").Where("account_id = ? ", user_id).Scopes(tools.Db_query_group(group, 100, 0), tools.Db_query_page(page, slice_target)).Find(&todolists).Error
if err != nil {
log.Error().Caller().Str("func", "db_conn.Order(\"id desc\")").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db query error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 根據資料量表示是否有後續分頁
if len(todolists) > 0 {
data := make(map[string]interface{})
for i, v := range todolists {
si := fmt.Sprint(i)
data[si] = gin.H{"id": v.ID,
"status": v.Status,
"subject": v.Subject}
}
if err := tools.Msg_send(c, "success", "list send", data); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
return
}
// 第一次啟動
} else if len(todolists) == 0 && page == 1 {
if err := tools.Msg_send(c, "success", "no data send", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
return
}
// 無分頁
} else {
if err := tools.Msg_send(c, "error", "no other page", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
return
}
}
}
```
1. **c.GetQuery(key)**
* query the url data by key, return value and bool.
1. **c.Set(key, value)**
* set value to used in other paths, functions.(擺脫變數的作用域)
---
## Delete
```go
type Delete_msg struct {
User_id string `json:"user_id"`
}
func delete(c *gin.Context) {
var dm Delete_msg
// 接收前端訊息
if err := c.ShouldBindJSON(&dm); err != nil {
log.Error().Caller().Str("func", "ShouldBindJSON(&subject)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "subject type error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
user_id, err := tools.Parse_int(dm.User_id)
if err != nil {
log.Error().Caller().Str("func", "tools.Parse_int(user_id_raw)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "user_id convert error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 開DB
db_conn, err := models.Db_conn_web(c)
defer models.Db_close(db_conn)
if err != nil {
log.Error().Caller().Str("func", "tools.Db_conn(c)").Err(err).Msg("Db")
if err := tools.Msg_send(c, "error", "db error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
var todolist []models.Todolist
count := 0
// 若資料量>100, 則分批刪除
for {
err := db_conn.Where("Status = ? AND account_id = ? ", 2, user_id).Limit(100).Select("ID", "Status").Find(&todolist).Error
if err != nil {
log.Error().Caller().Str("func", "db_conn.Where(\"Status = ?\", 2)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db query error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
break
}
// 判斷是否有資料,無資料則回傳"no object selected",以提示無資料經選取
if len(todolist) == 0 && count == 0 {
if err := tools.Msg_send(c, "error", "no object selected", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
break
// 無剩餘可刪除,刪除結束
} else if len(todolist) == 0 && count > 0 {
if err := tools.Msg_send(c, "success", "all deleted", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
break
// 有資料則更改status為0(軟刪除)並count++
} else {
err = db_conn.Model(&todolist).Update("Status", "0").Error
if err != nil {
log.Error().Caller().Str("func", " db_conn.Model(&todolist)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db deleted error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
break
}
count++
}
}
}
```
---
## update(patch, put)
```go
// Binding from JSON
type Update_subject struct {
Id string `json:"id" binding:"required,min=1"`
Subject string `json:"subject" binding:"required,min=1"`
Status string `json:"status" binding:"required,min=1"`
}
func update(c *gin.Context) {
// 接收前端訊息
var subject Update_subject
if err := c.ShouldBindJSON(&subject); err != nil {
log.Error().Caller().Str("func", "ShouldBindJSON(&subject)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "subject type error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// id string to int
subject_id, err := strconv.Atoi(subject.Id)
if err != nil {
log.Error().Caller().Str("func", "strconv.Atoi(subject.Id)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "id type error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// 開DB
db_conn, err := models.Db_conn_web(c)
defer models.Db_close(db_conn)
if err != nil {
log.Error().Caller().Str("func", "tools.Db_conn(c)").Err(err).Msg("Db")
if err := tools.Msg_send(c, "error", "db error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
var todolist models.Todolist
// 先找出欲異動資料
err = db_conn.Where("ID = ? AND Status <> ?", uint(subject_id), 0).Take(&todolist).Error
if err != nil {
log.Error().Caller().Str("func", "db_conn.Where(\"ID = ? AND Status <> ?\", uint(id_int), 0)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db query error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
// update
// complete則更新指定ID的status為2(完成的)
if subject.Status == "complete" {
// 更新
err = db_conn.Model(&todolist).Update("Status", "2").Error
if err != nil {
log.Error().Caller().Str("func", "db_conn.Model(&todolist).Update(\"Status\", \"2\")").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db updated error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
if err := tools.Msg_send(c, "success", "updated", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
// active則更新指定ID的status為1(未完成的)
} else if subject.Status == "active" {
// 更新
err = db_conn.Model(&todolist).Update("Status", "1").Error
if err != nil {
log.Error().Caller().Str("func", "db_conn.Model(&todolist).Update(\"Status\", \"1\")").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db updated error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
if err := tools.Msg_send(c, "success", "updated", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
// 判斷是否為更改標題
} else if subject.Status == "subject_change" {
err = db_conn.Model(&todolist).Update("Subject", subject.Subject).Error
if err != nil {
log.Error().Caller().Str("func", "db_conn.Model(&todolist).Update(\"Subject\", subject.Subject)").Err(err).Msg("Web")
if err := tools.Msg_send(c, "error", "db updated error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
return
}
if err := tools.Msg_send(c, "success", "updated", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
// 非以上則錯誤
} else {
log.Error().Caller().Str("func", "none").Str("msg", "status unknown").Msg("Web")
if err := tools.Msg_send(c, "error", "status error", nil); err != nil {
log.Error().Caller().Str("func", "tools.Msg_send").Err(err).Msg("Web")
}
}
}
```