编写银行 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
) 时未看到任何错误或输出,则表明它在正常运行。 打开 Web 浏览器并输入 URL http://localhost:8000/statement?number=1001
,或在程序运行时在另一个 shell 中运行以下命令:
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
) 时未看到任何错误或输出,则表明它在正常运行。 打开 Web 浏览器并输入 URL http://localhost:8000/deposit?number=1001&amount=100
,或在程序运行时在另一个 shell 中运行以下命令:
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
) 时未看到任何错误或输出,则表明它在正常运行。 打开 Web 浏览器并输入 URL http://localhost:8000/withdraw?number=1001&amount=100
,或在程序运行时在另一个 shell 中运行以下命令:
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,用于公开你从头构建的一个程序包中的功能。 请转到下一部分继续进行练习。 这次,你将编写自己的解决方案来完成一项挑战。