Fintech 은행 응용 프로그램 구축을 통해 Golang-4과: 사용자 인증 및 은행 이체 제1부분 학습

본고는 최초로 다음과 같이 발표되었다. https://www.blog.duomly.com/golang-course-with-building-a-fintech-banking-app-lesson-4-user-authentication-and-bank-transactions-part-1

소개


Golang 과정의 4과에서 우리는 사용자 신분 검증과 은행 거래에 대해 토론할 것이다.
이 과정의 첫 번째 부분에서 우리는 어떻게 이전하는지 배웠다.
Golang course with building a fintech banking app - Lesson 1: Start the project
우리는 사용자 로그인을 하는 방법을 배웠다.
Golang course with building a fintech banking app - Lesson 2: Login and REST API
사용자 등록 방법도 학습했습니다.
Golang course with building a fintech banking app - Lesson 3: User registration
그리고 제 친구 안나가 만든 Angular 9 과정을 기억해야 합니다.
Angular Course with building a banking application with Tailwind CSS - Lesson 1: Start the project
오늘 우리는 매우 재미있는 일에 관심을 가질 수 있는데, 이것은 우리 프로젝트의 주요 기능 중의 하나이다.
물론, 내가 지난번에 너에게 알려준 은행 이체를 가리킨다.
뿐만 아니라!
Golang 수업에서 사용자를 얻고 코드를 재구성하는 방법을 배울 것입니다.
JWT 영패를 사용하여 사용자 인증을 하는 방법도 가르쳐 드리겠습니다.
우리 시작합시다!
만약 당신이 동영상을 좋아한다면, 여기는 유튜브 버전입니다.

api에서 readBody를 생성합니다.가다


Golang 과정의 첫 번째 단계에서api를 재구성해야 합니다.가서 정리해.
대부분의 API 호출은 매우 비슷한 구조를 가지고 있기 때문에 우리는 항상 같은 코드를 반복할 필요가 없다.
우리는 우리가 몸을 읽는 데 쓰는 논리를 재구성할 수 있다.
api에서 readBody 함수를 만듭니다.여기로 이동해서 로그인과 등록 함수의 논리를 그곳으로 옮깁니다.
func readBody(r *http.Request) []byte {
    body, err := ioutil.ReadAll(r.Body)
    helpers.HandleErr(err)

    return body
}

api에서 apiResponse를 만듭니다.가다


우리가 API 호출에서 자주 사용하는 다음 논리는 API 응답과 논리적으로 관련이 있다.
우리는apiResponse라는 함수를 만들 수 있도록 비슷한 작업을 할 수 있습니다.
다음에 호출 응답과 관련된 모든 함수를 로 이동하고 대상의 이름을 "login", "register"에서 "call"으로 변경합니다.
func apiResponse(call map[string]interface{}, w http.ResponseWriter) {
    if call["message"] == "all is fine" {
        resp := call
        json.NewEncoder(w).Encode(resp)
        // Handle error in else
    } else {
        resp := interfaces.ErrResponse{Message: "Wrong username or password"}
        json.NewEncoder(w).Encode(resp)
    }
}

api의 로그인을 재구성합니다.readBody 및apiResponse 사용으로 이동


네, 우리의 정리 코드는 이미 준비가 되었습니다.
현재, 우리는 로그인 함수를 정리하고 우리의 함수로 모든 코드를 교체해야 한다.
읽기 주체와 관련된 논리와 응답과 관련된 논리를 교체합니다.
func login(w http.ResponseWriter, r *http.Request) {
    // Refactor login to use readBody
    body := readBody(r)

    var formattedBody Login
    err := json.Unmarshal(body, &formattedBody)
    helpers.HandleErr(err)

    login := users.Login(formattedBody.Username, formattedBody.Password)
    // Refactor login to use apiResponse function
    apiResponse(login, w)
}

api의 레지스터를 재구성합니다.readBody 및apiResponse 사용으로 이동


함수 "register"는 "login"함수와 같은 방식으로 정리해야 합니다.
읽기 주체와 응답만 바꾸면 됩니다.
func register(w http.ResponseWriter, r *http.Request) {
    body := readBody(r)

    var formattedBody Register
    err := json.Unmarshal(body, &formattedBody)
    helpers.HandleErr(err)

    register := users.Register(formattedBody.Username, formattedBody.Email, formattedBody.Password)
    // Refactor register to use apiResponse function
    apiResponse(register, w)
}

내부 오류를 처리하기 위해 PanicHandler 중간부품 만들기


다음 단계에서 우리는 공황 일지를 처리하는 데 집중해야 한다.
이런 것들은 모두 좋지 않다. 왜냐하면 무슨 일이 발생하면 응용 프로그램이 당황하기 시작하기 때문이다.
대부분의 경우, 사용자에게 어떤 오류가 있는지, 그리고 내부 오류가 있는지 알려주는 것이 가장 좋다.
우리는 아직 모든 오류를 단독으로 처리하지는 않겠지만, 내부 오류를 처리할 것이다.
이 점을 하려면 우리는 조수로 들어가야 한다.PanicHandler라는 중간부품을 만듭니다.
중간부품이 HTTP 호출을 차단합니다.
다음은 공황에서 회복될 것이다."내부 서버 오류"메시지가 있는 응답을 사용자에게 되돌려줍니다.
func PanicHandler(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            error := recover()
            if error != nil {
                log.Println(error)

                resp := interfaces.ErrResponse{Message: "Internal server error"}
                json.NewEncoder(w).Encode(resp)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

api에 PanicHandler를 추가합니다.go startApi 함수


우리의 코드가 준비된 후에, 우리는api에 들어가야 한다.라우터 선언 후 startApi 함수에 추가합니다.
func StartApi() {
    router := mux.NewRouter()
    // Add panic handler middleware
    router.Use(helpers.PanicHandler)
    router.HandleFunc("/login", login).Methods("POST")
    router.HandleFunc("/register", register).Methods("POST")
    fmt.Println("App is working on port :8888")
    log.Fatal(http.ListenAndServe(":8888", router))
}

ERR 응답을 인터페이스로 이동


이 단계에서 "ErrResponse"인터페이스를 "api.go"에서 제거해야 합니다.
Le't 인터페이스로 이동합니다.파일로 이동합니다.

사용자에서 GetUser 함수를 생성합니다.가다


너무 좋아요.
재구성이 곧 완성될 것이니 이제 우리는 사용자와 관련된 내용에 들어갈 수 있다.
첫 번째 단계는 사용자를 깊이 파고드는 것이다.GetUser라는 함수를 생성합니다.
이 함수는 두 개의 매개 변수를 사용해야 한다. 모두 문자열이고 첫 번째 매개 변수는'id', 두 번째 매개 변수는'jwt'이다.
GetUser 함수는 매핑 [문자열] 인터페이스 {} 를 반환해야 합니다.
func GetUser(id string, jwt string) map[string]interface{} {

}

인증 jwt 영패


만약 우리가 사용자와 관련된 활동을 하고 싶다면, 응용 프로그램은 우리가 계정의 소유자인지 아닌지를 알아야 한다.
우리는 JWT 영패를 검증함으로써 이 점을 검증할 수 있다.
JWT 인증을 만들기 위해서는helpers에 들어가야 합니다.ValidateToken이라는 함수를 만듭니다.
이 함수는 id와 jwtToken을 문자열로 하고 볼 값을 되돌려야 합니다.
주체 내부에서 우리는 영패에서'적재자'를 삭제하고 JWT 영패를 검증해야 한다.
다음은 인증된 영패의 id가 우리가 API에 보낸 id와 같은지 검증해야 합니다.
func ValidateToken(id string, jwtToken string) bool {
    cleanJWT := strings.Replace(jwtToken, "Bearer ", "", -1)
    tokenData := jwt.MapClaims{}
    token, err := jwt.ParseWithClaims(cleanJWT, tokenData, func(token *jwt.Token) (interface{}, error) {
            return []byte("TokenPassword"), nil
    })
    HandleErr(err)
    var userId, _ = strconv.ParseFloat(id, 8)
    if token.Valid && tokenData["user_id"] == userId {
        return true
    } else {
        return false
    }
}

withToken 기능을 prepareResponse에 추가


이제 사용자에게 돌아갈 수 있습니다.prepareResponse를 재구성합니다.
"withToken"이라는 세 번째 인자가 부울 값으로 사용될 변경 사항을 추가해야 합니다.
그리고 withToken이true일 때만 영패를 만들어야 합니다.
func prepareResponse(user *interfaces.User, accounts []interfaces.ResponseAccount, withToken bool) map[string]interface{} {
    responseUser := &interfaces.ResponseUser{
        ID: user.ID,
        Username: user.Username,
        Email: user.Email,
        Accounts: accounts,
    }
    var response = map[string]interface{}{"message": "all is fine"}
    // Add withToken feature to prepare response
    if withToken {
        var token = prepareToken(user);
        response["jwt"] = token
    }
    response["data"] = responseUser
    return response
}

로그인의 준비 응답과 사용자의 등록을 변경합니다.가다


로그인에 변경 사항을 추가하고 사용자 내부에 기능을 등록하는 것을 잊지 마십시오.가다
이 두 가지 상황에서 "prepareResponse"호출에 "true"를 세 번째 인자로 추가하기만 하면 됩니다.
func Login(username string, pass string) map[string]interface{} {
    // Add validation to login
    valid := helpers.Validation(
        []interfaces.Validation{
            {Value: username, Valid: "username"},
            {Value: pass, Valid: "password"},
        })
    if valid {
        // Connect DB
        db := helpers.ConnectDB()
        user := &interfaces.User{}
        if db.Where("username = ? ", username).First(&user).RecordNotFound() {
            return map[string]interface{}{"message": "User not found"}
        }
        // Verify password
        passErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass))

        if passErr == bcrypt.ErrMismatchedHashAndPassword && passErr != nil {
            return map[string]interface{}{"message": "Wrong password"}
        }
        // Find accounts for the user
        accounts := []interfaces.ResponseAccount{}
        db.Table("accounts").Select("id, name, balance").Where("user_id = ? ", user.ID).Scan(&accounts)

        defer db.Close()

        var response = prepareResponse(user, accounts, true);

        return response
    } else {
        return map[string]interface{}{"message": "not valid values"}
    }
}

// Create registration function
func Register(username string, email string, pass string) map[string]interface{} {
    // Add validation to registration
    valid := helpers.Validation(
        []interfaces.Validation{
            {Value: username, Valid: "username"},
            {Value: email, Valid: "email"},
            {Value: pass, Valid: "password"},
        })
    if valid {
        // Create registration logic
        // Connect DB
        db := helpers.ConnectDB()
        generatedPassword := helpers.HashAndSalt([]byte(pass))
        user := &interfaces.User{Username: username, Email: email, Password: generatedPassword}
        db.Create(&user)

        account := &interfaces.Account{Type: "Daily Account", Name: string(username + "'s" + " account"), Balance: 0, UserID: user.ID}
        db.Create(&account)

        defer db.Close()
        accounts := []interfaces.ResponseAccount{}
        respAccount := interfaces.ResponseAccount{ID: account.ID, Name: account.Name, Balance: int(account.Balance)}
        accounts = append(accounts, respAccount)
        var response = prepareResponse(user, accounts, true)

        return response
    } else {
        return map[string]interface{}{"message": "not valid values"}
    }

}

getUser에 영패 인증 추가


이 단계에서, 우리는 getUser로 돌아가려고 합니다.
우리가 여기서 해야 할 첫 번째 일은 영패 검증을 추가하는 것이다.
func GetUser(id string, jwt string) map[string]interface{} {
    isValid := helpers.ValidateToken(id, jwt)
    if isValid {

    } else {
        return map[string]interface{}{"message": "Not valid token"}
     }
}

사용자 찾기 및 복귀


만약 영패가 검증된다면, 우리는 논리를 시작할 수 있다.
만약 없다면, 우리는 회답을 돌려서 이 사실에 대한 정보를 제공해야 한다.
논리적으로 "Login"함수와 거의 같은 논리를 추가해야 합니다.
우리는 사용자를 찾아야 한다.이 예에서는 ID를 사용하여 사용자의 계정을 찾습니다.
"prepareResponse"호출에서만 마지막 인자는 "false"입니다.
func GetUser(id string, jwt string) map[string]interface{} {
    isValid := helpers.ValidateToken(id, jwt)
    // Find and return user
    if isValid {
        db := helpers.ConnectDB()
        user := &interfaces.User{}
        if db.Where("id = ? ", id).First(&user).RecordNotFound() {
            return map[string]interface{}{"message": "User not found"}
        }
        accounts := []interfaces.ResponseAccount{}
        db.Table("accounts").Select("id, name, balance").Where("user_id = ? ", user.ID).Scan(&accounts)

        defer db.Close()

        var response = prepareResponse(user, accounts, false);
        return response
    } else {
        return map[string]interface{}{"message": "Not valid token"}
    }
}

api에서 getUser 함수를 만듭니다.가다


우리와 사용자의 관계는 이미 끝났다.계속해서api로 돌아가자.가다
이 파일의 첫 번째 단계로 "getUser"라는 함수를 만들어야 합니다. 표준 API 호출로 응답과 요청을 매개 변수로 합니다.
함수 내부에서, 데이터베이스에서 추출해야 할 사용자 ID를 mux vars로 가져옵니다.
다음 중요한 것은 우리의 머리에서 권한을 얻고 GetUser 함수에 전달하는 것이다.
func getUser(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    userId := vars["id"]
    auth := r.Header.Get("Authorization")

    user := users.GetUser(userId, auth)
    apiResponse(user, w)
}

라우터를 추가합니다.Handle func는api에서 사용자를 가져오는 데 사용됩니다.가다


이제 라우팅에 다음 API 끝점을 추가할 수 있습니다.
StartApi에 추가하고 메서드를 GET로 설정해야 합니다.
 
func StartApi() {
    router := mux.NewRouter()
    router.Use(helpers.PanicHandler)
    router.HandleFunc("/login", login).Methods("POST")
    router.HandleFunc("/register", register).Methods("POST")
    router.HandleFunc("/user/{id}", getUser).Methods("GET")
    fmt.Println("App is working on port :8888")
    log.Fatal(http.ListenAndServe(":8888", router))
}

디렉터리 사용자 계정 만들기


우리는 이미 사용자를 얻는 일을 끝냈고, 우리는 은행 계좌에 들어갔다.
첫 번째 단계로, 우리는'useraccounts'라는 디렉터리를 만들어야 합니다.
프로젝트 루트 디렉터리에 이 디렉터리를 만듭니다.

파일 사용자 계정을 만듭니다.사용자 계정 포장하기


다음으로, 이 디렉터리에'useraccounts.go'라는 파일을 만들어야 합니다.
이 파일에서'useraccounts'라는 패키지를 설명해야 합니다.
package useraccounts

함수 updateAccount 만들기


오늘 Golang 수업에서 마지막 단계는 사용자의 은행 계좌를 업데이트할 수 있는 함수를 만드는 것입니다.
사용자 계정으로 들어가겠습니다."updateAccount"라는 함수를 만들고 로 이동합니다.
함수 내부에서 우리는 DB를 연결하고 계정을 업데이트하며 DB를 닫아야 한다.
DB를 자주 연결하거나 끊을 염려는 없습니다.우리는 다음 집중에서 연결 탱크를 연구할 것이다.
func updateAccount(id uint, amount int) {
    db := helpers.ConnectDB()
    db.Model(&interfaces.Account{}).Where("id = ?", id).Update("balance", amount)
    defer db.Close()
}

결론


축하합니다. 당신의 프로젝트는 현재 기능이 더욱 강해졌습니다!
저는 JWT 영패를 사용하여 사용자 인증을 하고 은행 이체 기능을 구축하는 방법을 알고 있습니다.
이것은 현재 과정의 코드 라이브러리입니다. https://github.com/Duomly/go-bank-backend/tree/Golang-course-Lesson-4
다음 회에서 우리는 은행 이체를 계속 연구하고 이 기능의 RESTAPI에 중점을 둘 것이다.
나는 지체하지 않고 너에게 이 모든 것을 가르쳐 주려고 한다. 너는 첫 번째 은행 이체를 할 것이다.
다음 몇 회는 Golang 수업에서 가장 관건적인 회이기 때문에 가장 중요한 기능을 구축할 것입니다.

읽어주셔서 감사합니다.
Duomly의 Radek.

좋은 웹페이지 즐겨찾기