# 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,
})
}
```

緊接著來做儲值以及提款功能
### 加值、存款功能
```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方法)