gin的第三天,千杯不醉。
今天的目標是要使用gin來製作一個可以提款、存款以及查詢餘額功能的個人小銀行。
一步一步來看要怎麼實作:
先來實作查詢功能
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,
	})
}

緊接著來做儲值以及提款功能
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,
	})
}
加值時需要判斷使用者填入的數字是不是正整數,而不是負數或其他亂填的阿雜符號。
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,
	})
}
不過這個第一版的個人小銀行,還不夠格成為一個夠成熟的API接口,還有些地方得改善,
像是在 操作成功時 通常不會出現message敘述,
只有在操作失敗時才會出現訊息,以提示使用者操作為何失敗。
裡面的邏輯及變數也有些冗餘,我們接著修改第二版。
與此同時,根據以下發送的參數,在操作成功後,
http://127.0.0.1/deposit/100
我們就可以從中取得到兩個資訊,
儲值 操作此筆儲值金額為100
相同地,提款也是如此
http://127.0.0.1/withdraw/10
考量到API的設計理念,
盡可能讓每個回傳參數攜帶最多的資訊、發揮最大的意義及功效。
我們可以這樣子改動 API返回的金額:
儲值多少錢 => 儲值後用戶餘額有多少
提款多少錢 => 提款後用戶餘額剩多少
同時也引入struct作為 gin Context回傳的json結構。
type Result struct {
	Amount  int    `json:"amount"`
	Status  string `json:"status"`
	Message string `json:"message"`
}
var result = Result{}
在設計較大型的專案API時,為了讓每個回傳的json格式、型別都一致,
(如果在接同個專案的不同接口時,API接口格式給的不一致,那麼處理上會Hen麻煩)
此時可另外設計一個wrapResponse function,
不論程式有沒有出現錯誤,都可以將gin.Context 作為參數傳遞給wrapResponse,
把所要回傳的值、型別、甚至err都集結起來,統一一個介面來作回傳。
如此一來也能更精簡化程式碼。
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)
}
Restful(Representational State Transfer) API是一種設計的概念,
其中理念是以資源對應的方式為主,讓每個資源對應Server上的一個URI(以路徑識別資源位置)。
不是硬性的特定規範,也沒有明確定義要如何實作,如何達成Restful全賴個人的設計。
設計時的大方向:
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方法)