撰寫銀行 API
建立了線上銀行核心邏輯後,讓我們建立一個 Web API,從瀏覽器 (或甚至命令列) 進行測試。 目前暫不使用資料庫保存資料,所以必須建立全域變數,將所有帳戶儲存在記憶體中。
而且,我們會略過測試部分,以免本指南保留太久。 理想狀況下,您會遵循我們建立核心套件時所遵循的相同方法,先撰寫測試再撰寫程式碼。
在記憶體中設定帳戶
我們不使用資料庫保存資料,而是使用記憶體對應在程式啟動時要建立的帳戶, 並會利用帳戶號碼使用對應存取帳戶資訊。
請移至 $GOPATH/src/bankapi/main.go
檔案並新增下列程式碼,以建立全域 accounts
變數,並使用帳戶予以初始化。 (這段程式碼類似我們之前建立測試時所建立的程式碼)。
package main
import (
"github.com/msft/bank"
)
var accounts = map[float64]*bank.Account{}
func main() {
accounts[1001] = &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
}
}
請確定您位於 $GOPATH/src/bankapi/
位置中。 以 go run main.go
執行程式,以確保未發生任何錯誤。 程式目前不會執行任何其他作業,所以,讓我們新增邏輯以建立 Web API。
公開對帳單方法
如您在上一個課程模組中所見,在 Go 中建立 Web API 很容易。 我們將繼續使用 net/http
套件。 我們也會使用 HandleFunc
和 ListenAndServe
函式公開端點並啟動伺服器。 HandleFunc
函式需要您想公開的 URL 路徑名稱,以及包含該端點邏輯的函式名稱。
就讓我們從公開功能以列印帳戶對帳單開始。 將下列程式碼複製貼入 main.go
:
func statement(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account number!")
} else {
account, ok := accounts[number]
if !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else {
fmt.Fprintf(w, account.Statement())
}
}
}
statement
函式的第一個醒目提示是其正在接收物件,以將回應寫回瀏覽器 (w http.ResponseWriter
)。 其也在接收要求物件,以從 HTTP 要求存取資訊號 (req *http.Request
)。
然後,請注意,我們會使用 req.URL.Query().Get()
函式從查詢字串中讀取參數。 此參數是我們將要透過 HTTP 呼叫傳送的帳戶號碼。 我們將使用此值存取帳戶對應並取得其資訊。
因為我們要向使用者索取資料,所以應該包含一些驗證以免當機。 當我們知道帳戶號碼為有效時,就可以呼叫 Statement()
方法,並列印出其要傳到瀏覽器的字串 (fmt.Fprintf(w, account.Statement())
)。
現在,將您的 main()
函式修改成這樣:
func main() {
accounts[1001] = &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
}
http.HandleFunc("/statement", statement)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
如果執行程式 (go run main.go
) 後未看到任何錯誤或輸出,表示運作正常。 開啟網頁瀏覽器並輸入 URL http://localhost:8000/statement?number=1001
,或在程式執行時在另一個殼層中執行下列命令:
curl http://localhost:8000/statement?number=1001
您應該會看見下列輸出:
1001 - John - 0
公開存款方法
讓我們繼續使用相同的方法公開存款方法。 在此案例中,我們想要把錢加入記憶體中的帳戶。 每次呼叫 Deposit()
方法時,餘額應該都會增加。
在主程式中,新增如下的 deposit()
函式。 此函式會從查詢字串取得帳戶號碼、驗證該帳戶是否存在於 accounts
對應中、驗證存款金額是否為有效數字,然後呼叫 Deposit()
方法。
func deposit(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
amountqs := req.URL.Query().Get("amount")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account number!")
} else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
fmt.Fprintf(w, "Invalid amount number!")
} else {
account, ok := accounts[number]
if !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else {
err := account.Deposit(amount)
if err != nil {
fmt.Fprintf(w, "%v", err)
} else {
fmt.Fprintf(w, account.Statement())
}
}
}
}
請注意,此函式會遵循類似的方法,取得並驗證從使用者處收到的資料。 我們也會直接在 if
陳述式中宣告和使用變數。 最後,在帳戶中存入一些錢後,我們會列印對帳單,查看新的帳戶餘額。
現在,您應該公開呼叫 deposit
函式的 /deposit
端點。 將您的 main()
函式修改成這樣:
func main() {
accounts[1001] = &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
}
http.HandleFunc("/statement", statement)
http.HandleFunc("/deposit", deposit)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
如果執行程式 (go run main.go
) 後未看到任何錯誤或輸出,表示運作正常。 開啟網頁瀏覽器並輸入 URL http://localhost:8000/deposit?number=1001&amount=100
,或在程式執行時在另一個殼層中執行下列命令:
curl "http://localhost:8000/deposit?number=1001&amount=100"
您應該會看見下列輸出:
1001 - John - 100
如果執行數次相同的呼叫,帳戶餘額將會持續增加。 請嘗試確認記憶體中的 accounts
對應已在執行階段更新。 如果停止此程式,您執行的所有存款作業都會遺失,但這是此初始版本應有的結果。
公開提款方法
最後,讓我們公開從帳戶提款的方法。 同樣地,讓我們先在主程式中建立 withdraw
函式。 此函式會驗證帳戶號碼資訊、提款,以及列印您從核心套件收到的所有錯誤。 將下列函式新增至您的主程式:
func withdraw(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
amountqs := req.URL.Query().Get("amount")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account number!")
} else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
fmt.Fprintf(w, "Invalid amount number!")
} else {
account, ok := accounts[number]
if !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else {
err := account.Withdraw(amount)
if err != nil {
fmt.Fprintf(w, "%v", err)
} else {
fmt.Fprintf(w, account.Statement())
}
}
}
}
現在,在 main()
函式中新增 /withdraw
端點,公開您在 withdraw()
函式中的邏輯。 將您的 main()
函式修改成這樣:
func main() {
accounts[1001] = &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
}
http.HandleFunc("/statement", statement)
http.HandleFunc("/deposit", deposit)
http.HandleFunc("/withdraw", withdraw)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
如果執行程式 (go run main.go
) 後未看到任何錯誤或輸出,表示運作正常。 開啟網頁瀏覽器並輸入 URL http://localhost:8000/withdraw?number=1001&amount=100
,或在程式執行時在另一個殼層中執行下列命令:
curl "http://localhost:8000/withdraw?number=1001&amount=100"
您應該會看見下列輸出:
the amount to withdraw should be greater than the account's balance
請注意,我們收到的錯誤是來自核心套件。 當程式啟動時,帳戶餘額為零。 因此,您無法提出任何金額。 呼叫幾次 /deposit
端點加入金額,再次呼叫 /withdraw
端點,以確認運作正常:
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/withdraw?number=1001&amount=100"
您應該會看見下列輸出:
1001 - John - 200
介紹完畢 您已建立 Web API,可公開您從無到有所建立之套件中的功能。 前往下一節繼續練習。 這次的挑戰是撰寫您專屬的解決方案。