前言

之前已经对Go的基本语法有了大致的了解,但想真正的学会一门语言还有很长的路要走。今天学习了教程中的web银行项目,收获了不少的东西。 往期文章:Golang(1)——新人手册 #掘金文章# juejin.cn/post/695215…

项目规划

本项目是基于Go实现一个基于web的银行服务,其基本功能如下:

  1. 显示账户信息:用户名、手机类型、电话、余额等;
  2. 存款;
  3. 取款;
  4. 转账(即A账户给B账户转账);
  5. 自动化测试功能;

通过这个项目能学到什么?

  • 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的基本语法。

(未完待续)