Эта статья изначально была опубликована по адресу: https://www.blog.duomly.com/golang-course-with-building-a-fintech-banking-app-lesson-5-bank-transactions-part-2/

Введение в курс Golang Банковские операции

Введение в курс Golang Банковские операции

В Уроке 5 курса Голанг мы продолжим банковские операции.

В предыдущих выпусках курса мы узнали, как выполнять миграцию:
Курс Golang с созданием банковского приложения для финансовых технологий - Урок 1: Запуск проекта

Мы узнали, как выполнить вход пользователя:
Курс Golang по созданию банковского приложения для финансовых технологий - Урок 2: Вход и REST API

Мы узнали, как регистрировать пользователей:
Курс Golang с созданием банковского приложения для финансовых технологий - Урок 3: Регистрация пользователя

Мы создали аутентификацию пользователей и начали с транзакций:
Курс Golang по созданию банковского приложения Fintech - Урок 4: Аутентификация пользователя и банковские транзакции, ЧАСТЬ 1

Также вам нужно помнить о курсе Angular 9, созданном моей подругой Анной:
Angular курс по созданию банковского приложения с Tailwind CSS - Урок 1: Запуск проекта

Сегодня мы остановимся на следующей части банковских операций.

Мы создадим новый модуль с именем transaction, который мы будем использовать для создания истории транзакций, но не только!

Как и в остальной части сегодняшнего урока, мы проведем небольшой рефакторинг, создадим логику осуществления банковских переводов и обработаем новые конечные точки API.

Давайте начнем!

Если вы предпочитаете видео, вот версия для YouTube:

Создайте интерфейс транзакции

В качестве первого шага нам нужно подготовить некоторый интерфейс транзакций, который мы будем использовать для логики, связанной с БД.

Для этого нам нужно зайти в файл interfaces.go и создать структуру с именем «Transaction».

Этот элемент должен содержать следующие реквизиты: gorm.Model, From as uint, To as uint и Amount as int.

Взгляните на пример:

type Transaction struct {
  gorm.Model
  From uint
  To uint
  Amount int
}

Создайте функцию MigrateTransactions в migrations.go

На следующем этапе мы должны сосредоточиться на реализации транзакций внутри базы данных.

Для этого мы могли бы обновить функцию с именем «Migrate», и даже это лучший способ сделать это.

Но в этом случае я создам отдельную функцию, чтобы не путать ваши миграции с предыдущими.

Нам нужно зайти в файл migrations.go и создать функцию с именем «MigrateTransactions».

Внутри этой функции мы должны создать логику миграции БД, но связанную с интерфейсом с именем «Транзакции».

func MigrateTransactions() {
    Transactions := &interfaces.Transaction{}
    db := helpers.ConnectDB()
    db.AutoMigrate(&Transactions)
    defer db.Close()
}

Создать функцию getAccount

Следующая логика, которая нам понадобится, - это функция, которая найдет аккаунт по ID и вернет его.

Для этого в качестве первого шага нам нужно зайти в useraccounts.go и создать функцию с именем «getAccount».

Внутри функции нам нужно подключиться к БД, найти учетную запись по id и вернуться.

func getAccount(id uint) *interfaces.Account{
    db := helpers.ConnectDB()
    account := &interfaces.Account{}
    if db.Where("id = ? ", id).First(&account).RecordNotFound() {
        return nil
    }
    defer db.Close()
    return account
}

Создать функцию Transaction

Затем мы можем перейти к более увлекательной части урока - транзакции.

Чтобы начать работать с этой логикой, мы должны остаться в том же файле useraccounts.go.

В качестве первого шага мы должны создать функцию с именем «Транзакция» с несколькими параметрами и ответом в виде map [string] interface {}.

Params должны быть userId, from и to, в качестве uint, сумма должна быть int, а jwt - строкой.

func Transaction(userId uint, from uint, to uint, amount int, jwt string) map[string]interface{} {
}

Преобразовать uint в строку

Следующей частью функции Transaction должно быть действие, которое преобразует userId из uint в строку.

Мы делаем это только потому, что ValidateToken принимает userId как строку. Мы можем реорганизовать это позже, когда у нас будет больше мест, которые используют валидацию, а не из GET.

userIdString := fmt.Sprint(userId)

Проверить jwt

Как и в предыдущем эпизоде, сегодня мы должны проверить токен jwt.

Внутри функции «Транзакция» нам нужно создать такой же оператор if / else с проверкой.

func Transaction(userId uint, from uint, to uint, amount int, jwt string) map[string]interface{} {
    userIdString := fmt.Sprint(userId)
    isValid := helpers.ValidateToken(userIdString, jwt)
    if isValid {
    } else {
        return map[string]interface{}{"message": "Not valid token"}
    }
}

Возьмите отправителя и получателя

Затем мы должны взять две учетные записи, обе с использованием функции «getAccount», которую мы создали ранее.
Первая - это учетная запись отправителя, назначенная переменной с именем «fromAccount».

Второй - получатель, назначенный для «toAccount».

fromAccount := getAccount(from)
toAccount := getAccount(to)

Обработка ошибок

В следующей части мы должны поискать несколько ошибок и проверить, все ли в порядке.

Первый и очень важный момент - проверить, существуют ли обе учетные записи.

Следующий - проверить, являемся ли мы владельцем учетной записи.

Последний - проверить, достаточно ли у нас денег для создания перевода.

if fromAccount == nil || toAccount == nil {
  return map[string]interface{}{"message": "Account not found"}
} else if fromAccount.UserID != userId {
  return map[string]interface{}{"message": "You are not owner of the account"}
} else if int(fromAccount.Balance) < amount {
  return map[string]interface{}{"message": "Account balance is too small"}
}

Обновить аккаунт

Вся логика проверена, если наши данные в порядке, и мы можем выполнить передачу.

Далее мы можем отправить деньги.

Чтобы сохранить в БД, мы отправили деньги, и получатель их получил, мы должны обновить наши банковские счета.

Нам необходимо обновить наш счет и списать деньги с нашего баланса.

И мы должны сделать то же самое со счетом получателя с одной небольшой разницей, мы должны увеличить его баланс.

updatedAccount := updateAccount(from, int(fromAccount.Balance) - amount)
updateAccount(to, int(toAccount.Balance) + amount)

Создать транзакцию

Когда передача будет завершена, мы должны где-нибудь сохранить информацию об этом.

Чтобы информация о переводе была в нашей истории, нам нужно создать транзакцию, вызвав функцию «CreateTransaction».

Мы создадим эту функцию на следующих шагах, а теперь просто добавьте вызов.

transactions.CreateTransaction(from, to, amount)

Ответить

Поскольку последний шаг в функции «Транзакция» - это подготовка ответа и его возврат.

var response = map[string]interface{}{"message": "all is fine"}
response["data"] = updatedAccount
return response

Обновить функцию updateAccount

Пока мы не перейдем от useraccounts.go, нам нужно обновить функцию с именем updateAccount.

Во-первых, нам нужно реорганизовать способ обновления учетной записи, чтобы позже у нас был более простой способ манипулировать ею с помощью обновленных данных.

Затем мы должны добавить некоторую логику, которая подготовит ответ учетной записи и вернет его.

func updateAccount(id uint, amount int) interfaces.ResponseAccount {
    db := helpers.ConnectDB()
    account := interfaces.Account{}
    responseAcc := interfaces.ResponseAccount{}
    db.Where("id = ? ", id).First(&account)
    account.Balance = uint(amount)
    db.Save(&account)
    responseAcc.ID = account.ID
    responseAcc.Name = account.Name
    responseAcc.Balance = int(account.Balance)
    defer db.Close()
    return responseAcc
}

Создание транзакций модуля

Когда мы закончили с useraccounts.go, мы можем перейти в модуль «транзакции».

Во-первых, нам нужно создать каталог с именем «транзакции».

Внутри каталога нам нужно создать файл с таким же именем и объявить внутри пакет с именем «transaction».

package transactions
import (
    "duomly.com/go-bank-backend/helpers"
    "duomly.com/go-bank-backend/interfaces"
)

Создать функцию CreateTransaction

Затем внутри файла transaction.go мы должны создать функцию с именем «CreateTransaction».

Внутри функции нам нужно добавить логику, которая будет отвечать за создание новой записи в базе данных.

func CreateTransaction(From uint, To uint, Amount int) {
    db := helpers.ConnectDB()
    transaction := &interfaces.Transaction{From: From, To: To, Amount: Amount}
    db.Create(&transaction)
    defer db.Close()
}

Создайте интерфейс TransactionBody в api.go

На этом этапе мы можем перейти к api.go, это будет последний файл, в котором мы разрабатываем код.

В качестве первого шага в этом файле мы должны создать интерфейс с именем «TransactionBody».

Этот интерфейс будет отвечать за тело вызова транзакции.

type TransactionBody struct {
    UserId uint
    From uint
    To uint
    Amount int
}

Рефакторинг apiResponse

Затем нам нужно внести одно небольшое изменение в наш apiResponse.

Мы должны вернуть всю переменную вызова вместо «данных».

func apiResponse(call map[string]interface{}, w http.ResponseWriter) {
    if call["message"] == "all is fine" {
        resp := call
        json.NewEncoder(w).Encode(resp)
    } else {
        resp := call
        json.NewEncoder(w).Encode(resp)
    }
}

Создать транзакцию функции

На последнем этапе создания логики нам нужно обработать вызов API.

Для этого мы должны создать функцию с именем «транзакция».

Практически вся логика будет аналогична остальным вызовам API. Тем не менее, в этом случае нам нужно помнить об обработке аутентификации и использовать интерфейс, который мы создали ранее.

func transaction(w http.ResponseWriter, r *http.Request) {
    body := readBody(r)
    auth := r.Header.Get("Authorization")
    var formattedBody TransactionBody
    err := json.Unmarshal(body, &formattedBody)
    helpers.HandleErr(err)
    transaction := useraccounts.Transaction(formattedBody.UserId, formattedBody.From, formattedBody.To, formattedBody.Amount, auth)
    apiResponse(transaction, w)
}

Обработка конечной точки в маршрутизаторе

Теперь осталось только обработать конечную точку «/ transaction» в маршрутизации.

Передайте функцию «транзакция» и используйте метод «POST».

func StartApi() {
    router := mux.NewRouter()
    router.Use(helpers.PanicHandler)
    router.HandleFunc("/login", login).Methods("POST")
    router.HandleFunc("/register", register).Methods("POST")
    router.HandleFunc("/transaction", transaction).Methods("POST")
    router.HandleFunc("/user/{id}", getUser).Methods("GET")
    fmt.Println("App is working on port :8888")
    log.Fatal(http.ListenAndServe(":8888", router))
}

Сделайте миграцию

Весь код готов!

Теперь мы можем приступить к миграции.

Как и в первом уроке, нам нужно зайти в main.go и добавить вызов «MigrateTransactions» в функцию «main».

Не забудьте прокомментировать «api.StartApi ()».

import "duomly.com/go-bank-backend/migrations"
func main() {
    migrations.MigrateTransactions()
    // api.StartApi()
}

Вывод

Поздравляем, теперь вы можете сделать первый банковский перевод!

Я очень рад, что функции вашего проекта стали намного более продвинутыми, и он выглядит намного более законченным.

Вот репозиторий кода для текущего урока:
https://github.com/Duomly/go-bank-backend/tree/Golang-course-Lesson-5

В следующем выпуске мы сосредоточимся на оптимизации производительности и рефакторинге соединений с базой данных.

Это даст нам намного лучшую производительность, устранит некоторые опасности, такие как убийства DB при оверколе, и поможет нам сделать приложение намного более стабильным.

Будьте в курсе и подписывайтесь на нас, потому что я не могу дождаться, когда мы создадим еще больше кода!

Спасибо за внимание,
Радек из Дуомли.