撰寫銀行 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 套件。 我們也會使用 HandleFuncListenAndServe 函式公開端點並啟動伺服器。 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,可公開您從無到有所建立之套件中的功能。 前往下一節繼續練習。 這次的挑戰是撰寫您專屬的解決方案。