前言
之前已经对Go的基本语法有了大致的了解,但想真正的学会一门语言还有很长的路要走。今天学习了教程中的web银行项目,收获了不少的东西。 往期文章:Golang(1)——新人手册 #掘金文章# juejin.cn/post/695215…
项目规划
本项目是基于Go实现一个基于web的银行服务,其基本功能如下:
- 显示账户信息:用户名、手机类型、电话、余额等;
- 存款;
- 取款;
- 转账(即A账户给B账户转账);
- 自动化测试功能;
通过这个项目能学到什么?
- Go项目的基本架构;
- net/http的使用;
- Go自动化测试代码的编写;
项目架构
本次项目的代码树见下:
其中,bankapi是程序主函数,负责调用bankcore中提供的函数接口,实现http相应;bankcore负责实现银行操作的函数;
项目细节
项目代码见后,有关代码的详细解释清参考注释:
//main.go
package main
import (
"encoding/json"
"fmt"
"github.com/msft/bank"
"log"
"net/http"
"strconv"
)
// CustomAccount ...
type CustomAccount struct {
*bank.Account
}
// Statement ...
func (c *CustomAccount) Statement() string {
json, err := json.Marshal(c)
if err != nil {
return err.Error()
}
return string(json)
}
//float和account的映射
var accounts = map[float64]*CustomAccount{}
func main() {
accounts[1001] = &CustomAccount{
Account: &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
},
}
accounts[1002] = &CustomAccount{
Account: &bank.Account{
Customer: bank.Customer{
Name: "Mark",
Address: "Irvine, California",
Phone: "(949) 555 0198",
},
Number: 1002,
},
}
//handler相当于是回调函数:用户访问之后,触发的处理函数
http.HandleFunc("/statement", statement)
http.HandleFunc("/deposit", deposit)
http.HandleFunc("/withdraw", withdraw)
http.HandleFunc("/transfer", transfer)
//使用指定的监听地址和处理器启动一个HTTP服务端
//处理器参数通常是nil,这表示采用包变量DefaultServeMux作为处理器。
//Handle和HandleFunc函数可以向DefaultServeMux添加处理器
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
//存钱的回调函数
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())
}
}
}
}
//取钱的回调函数
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())
}
}
}
}
//转账的回调函数
func transfer(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
amountqs := req.URL.Query().Get("amount")
destqs := req.URL.Query().Get("dest")
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 if dest, err := strconv.ParseFloat(destqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account destination number!")
} else {
if accountA, ok := accounts[number]; !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else if accountB, ok := accounts[dest]; !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", dest)
} else {
err := accountA.Transfer(amount, accountB.Account)
if err != nil {
fmt.Fprintf(w, "%v", err)
} else {
fmt.Fprintf(w, accountA.Statement())
}
}
}
}
//打印信息的回调函数
func statement(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
number, err := strconv.ParseFloat(numberqs, 64)
if 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 {
json.NewEncoder(w).Encode(bank.Statement(account))
}
}
}
//bank.go
package bank
import (
"errors"
"fmt"
)
// Customer ...
type Customer struct {
Name string
Address string
Phone string
}
// Account ...
type Account struct {
Customer
Number int32
Balance float64
}
// Deposit 存钱
func (a *Account) Deposit(amount float64) error {
if amount <= 0 {
return errors.New("the amount to deposit should be greater than zero")
}
a.Balance += amount
return nil
}
// Withdraw 取钱
func (a *Account) Withdraw(amount float64) error {
if amount <= 0 {
return errors.New("the amount to withdraw should be greater than zero")
}
if a.Balance < amount {
return errors.New("the amount to withdraw should be greater than the account's balance")
}
a.Balance -= amount
return nil
}
// Statement ...
func (a *Account) Statement() string {
return fmt.Sprintf("%v - %v - %v", a.Number, a.Name, a.Balance)
}
// Transfer function
func (a *Account) Transfer(amount float64, dest *Account) error {
if amount <= 0 {
return errors.New("the amount to transfer should be greater than zero")
}
if a.Balance < amount {
return errors.New("the amount to transfer should be greater than the account's balance")
}
a.Withdraw(amount)
dest.Deposit(amount)
return nil
}
// Bank ...
type Bank interface {
Statement() string
}
// Statement ...
func Statement(b Bank) string {
return b.Statement()
}
其中模块配置如下:
//bankapi/go.mod
module bankapi
go 1.16
require (
github.com/msft/bank v0.0.1
)
replace github.com/msft/bank => ../bankcore
//bankcore/go.mod module github.com/msft/bank go 1.16
其实上述项目整体运行流程可以以下面这个框图表示:
结果展示
由于现阶段还没有将HTML与GO相结合,因此网页显示的比较粗糙,并且相应的HTTP请求只能通过修改url来实现,具体的功能如下:
显示账号信息:
http://localhost:8000/statement?number=1001
在浏览器中输入上述url,即可显示对应账户的信息(代码中仅有两个账号1002 1001),number=1001即指明账户是1001。结果为:
存款:
http://localhost:8000/deposit?number=1001&amount=1000
上述url意指在1001账户中存款1000,结果如下:
取款呢?
http://localhost:8000/withdraw?number=1001&amount=1000
刚刚存的1000又被取出来了:
可不可以转账?
http://localhost:8000/transfer?number=1001&amount=1000&dest=1002
将1001账户转了1000到1002账户(原本1002没钱的),结果如下:
后续我会写一个HTML网页,来可视化这些操作(通过按钮)。希望大家可以通过这个项目来尽快熟悉Go的基本语法。
(未完待续)