# Day22 Golang 網頁框架 gin 實作小專案 (RESTful API) gin的第三天,千杯不醉。 --- 今天的目標是要使用gin來製作一個可以提款、存款以及查詢餘額功能的**個人小銀行**。 一步一步來看要怎麼實作: 先來實作查詢功能 ### 查詢餘額功能 ```go package main import ( "net/http" "strconv" "github.com/gin-gonic/gin" ) var balance = 1000 func main() { router := gin.Default() router.GET("/balance/", getBalance) router.Run(":80") } func getBalance(context *gin.Context) { var msg = "您的帳戶內有:" + strconv.Itoa(balance) + "元" context.JSON(http.StatusOK, gin.H{ "amount": balance, "status": "ok", "message": msg, }) } ``` ![查詢餘額](https://i.imgur.com/0uHeTaw.png) 緊接著來做儲值以及提款功能 ### 加值、存款功能 ```go func deposit(context *gin.Context) { var status string var msg string input := context.Param("input") amount, err := strconv.Atoi(input) if err == nil { if amount <= 0 { amount = 0 status = "failed" msg = "操作失敗,存款金額需大於0元!" } else { balance += amount status = "ok" msg = "已成功存款" + strconv.Itoa(amount) + "元" } } else { amount = 0 status = "failed" msg = "操作失敗,輸入有誤!" } context.JSON(http.StatusOK, gin.H{ "amount": amount, "status": status, "message": msg, }) } ``` 加值時需要判斷使用者填入的數字是不是正整數,而不是負數或其他亂填的阿雜符號。 ### 提款功能 ```go func withdraw(context *gin.Context) { var status string var msg string input := context.Param("input") amount, err := strconv.Atoi(input) if err == nil { if amount <= 0 { amount = 0 status = "failed" msg = "操作失敗,提款金額需大於0元!" } else { if balance-amount < 0 { amount = 0 status = "failed" msg = "操作失敗,餘額不足!" } else { balance -= amount status = "ok" msg = "成功提款" + strconv.Itoa(amount) + "元" } } } else { amount = 0 status = "failed" msg = "操作失敗,輸入有誤!" } context.JSON(http.StatusOK, gin.H{ "amount": amount, "status": status, "message": msg, }) } ``` [**完整程式碼**](https://github.com/gjlmotea/ithelp/blob/main/day22_gin/bank1/bank1.go) 不過這個第一版的個人小銀行,還不夠格成為一個夠成熟的API接口,還有些地方得改善, 像是在 **操作成功時** 通常不會出現`message`敘述, **只有在操作失敗時才會出現訊息**,以提示使用者操作為何失敗。 裡面的邏輯及變數也有些冗餘,我們接著修改第二版。 ## 賦予API返回值的意義 與此同時,根據以下發送的參數,在操作成功後, http://127.0.0.1/deposit/100 我們就可以從中取得到兩個資訊, 1. 這是`儲值` 操作 2. 並且`此筆儲值金額為100` 相同地,提款也是如此 http://127.0.0.1/withdraw/10 考量到API的設計理念, > 盡可能讓每個回傳參數攜帶最多的資訊、發揮最大的意義及功效。 我們可以這樣子改動 API返回的金額: `儲值多少錢` => `儲值後用戶餘額有多少` `提款多少錢` => `提款後用戶餘額剩多少` **同時也引入`struct`作為 gin Context回傳的json結構。** ```go type Result struct { Amount int `json:"amount"` Status string `json:"status"` Message string `json:"message"` } var result = Result{} ``` [**改動後的程式碼**](https://github.com/gjlmotea/ithelp/blob/main/day22_gin/bank2/bank2.go) ## 導入回傳值的樣板 wrapResponse 在設計較大型的專案API時,為了讓每個回傳的json格式、型別都一致, (如果在接同個專案的不同接口時,API接口格式給的不一致,那麼處理上會Hen麻煩) 此時可另外設計一個`wrapResponse function`, 不論程式有沒有出現錯誤,都可以將`gin.Context` 作為參數傳遞給`wrapResponse`, 把所要回傳的值、型別、甚至`err`都集結起來,統一一個`介面`來作回傳。 如此一來也能更精簡化程式碼。 ```go func wrapResponse(context *gin.Context, amount int, err error) { var r = struct { Amount int `json:"amount"` Status string `json:"status"` Message string `json:"message"` }{ Amount: amount, Status: "ok", // 預設狀態為ok Message: "", } if err != nil { r.Amount = 0 r.Status = "failed" // 若出現任何err,狀態改為failed r.Message = err.Error() // Message回傳錯誤訊息 } context.JSON(http.StatusOK, r) } ``` [**成熟版的程式碼**](https://github.com/gjlmotea/ithelp/blob/main/day22_gin/bank3/bank3.go) --- ## Restful API Restful(Representational State Transfer) API是一種設計的概念, 其中理念是以**資源對應**的方式為主,讓每個資源對應Server上的一個URI(以路徑識別資源位置)。 不是硬性的特定規範,也沒有明確定義要如何實作,如何達成Restful全賴個人的設計。 設計時的大方向: * 必要的參數以路徑參數為主,選填的參數以查詢參數為主 * 靈活運用Method,減少**動詞**的使用 > GET is used to request data from a specified resource. > PUT/POST is used to send data to a server to create/update a resource. > The DELETE method deletes the specified resource. 以**訊息**為例,原先**查詢訊息、發布訊息、刪除訊息** 3支動作的API, 可如下修改,將其命名為相同的`func`: getMsg => msg (GET方法) createMsg => msg (POST方法) deleteMsg => msg (DELETE方法)