Improve login process

We have completed the registration process in the last article, now we just need to improve our login mechanism as before

Define login parameters

type ParamLogin struct {
   UserName string `json:"username" binding:"required"`
   Password string `json:"password" binding:"required"`
}
Copy the code

Define the controller for logging in

func LoginHandler(c *gin.Context) { p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err ! = nil { zap.L().Error("LoginHandler with invalid param", Zap. Error(err)) // The json format Error is not a validator Error and cannot be translated. So here to do type judgment is incremented, ok: = err. (the validator. ValidationErrors) if! ok { c.JSON(http.StatusOK, gin.H{ "msg": err.Error(), }) } else { c.JSON(http.StatusOK, gin.H{ "msg": RemoveTopStruct (errs.translate (trans)),})} Return} err := logic.login (p) if err! Zap.l ().error ("login failed", zap.string ("username", p.user name), zap.Error(err)) c.JSON(http.StatusOK, gin.H{ "msg": C. SON(http.statusok, "login success")}Copy the code

Define logins logic

func Login(login *models.ParamLogin) error {
   user := models.User{
      Username: login.UserName,
      Password: login.Password,
   }
   return mysql.Login(&user)
}
Copy the code

Finally, take a look at the DAO layer for logging in

func Login(user *models.User) error { oldPassword := user.Password sqlStr := `select user_id,username,password from user  where username=? 'err := db.Get(user, sqlStr, user.username) if err == sql.ErrNoRows {return errors.New(" The user does not exist ")} if err! = nil { return err } if encryptPassword(oldPassword) ! = user.password {return errors.New(" incorrect Password ")} return nil}Copy the code

Encapsulate our response methods

After completing the login and registration methods, we will find that the process is a little redundant, and the response method is a bit of duplicate code, so we will try to optimize it

Let’s start by defining our Response code

package controllers type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeInvalidPassword CodeServerBusy ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "Request parameter error ", CodeUserExist:" User already exists ", CodeInvalidPassword: "User name or password is incorrect ", CodeServerBusy: Msg() string {Msg, ok := codeMsgMap[c] if! ok { msg = codeMsgMap[CodeServerBusy] } return msg }Copy the code

And then we define our response function

package controllers

import (
   "net/http"

   "github.com/gin-gonic/gin"
)

type Response struct {
   Code ResCode     `json:"code"`
   Msg  interface{} `json:"msg"`
   Data interface{} `json:"data"`
}

func ResponseError(c *gin.Context, code ResCode) {
   c.JSON(http.StatusOK, &Response{
      Code: code,
      Msg:  code.Msg(),
      Data: nil,
   })
}

func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) {
   c.JSON(http.StatusOK, &Response{
      Code: code,
      Msg:  msg,
      Data: nil,
   })
}

func ResponseSuccess(c *gin.Context, data interface{}) {

   c.JSON(http.StatusOK, &Response{
      Code: CodeSuccess,
      Msg:  CodeSuccess.Msg(),
      Data: data,
   })
}
Copy the code

By the way, go to the DAO layer and define our errors as constants

package mysql import ( "crypto/md5" "database/sql" "encoding/hex" "errors" "go_web_app/models" "go.uber.org/zap" ) const Serect = "wuyue.com" // Define an error constant for determining WrongPassword = var (UserAleadyExists = errors.New(" User already exists ") WrongPassword = Errors. New(" incorrect password ") UserNoExists = errors.New(" user does not exist ")) // The dao layer encapsulates the database operation as a function waiting for the logic layer to call her func InsertUser(user Password = encryptPassword(user.password) SQLSTR := 'insert into user(user_id,username,password) values(?,?,?)` _, err := db.Exec(sqlstr, user.UserId, user.Username, user.Password) if err ! = nil { zap.L().Error("InsertUser dn error", zap.Error(err)) return err } return nil } // func Login(user *models.User) error { oldPassword := user.Password sqlStr := `select user_id,username,password from user where username=? ` err := db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return UserNoExists } if err ! = nil { return err } if encryptPassword(oldPassword) ! = user.Password {return WrongPassword} return nil} // CheckUserExist Checks whether the database has the username func CheckUserExist(username) string) error { sqlstr := `select count(user_id) from user where username = ? ` var count int err := db.Get(&count, sqlstr, username) if err ! = nil { zap.L().Error("CheckUserExist dn error", Zap. Error(err)) return err} if count > 0 {return UserAleadyExists} return nil} // encryptPassword(password  string) string { h := md5.New() h.Write([]byte(serect)) return hex.EncodeToString(h.Sum([]byte(password))) }Copy the code

And finally, what about the Controller layer

I’ll focus on errors.Is here

package controllers import ( "errors" "go_web_app/dao/mysql" "go_web_app/logic" "go_web_app/models" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) func LoginHandler(c *gin.Context) { p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err ! = nil { zap.L().Error("LoginHandler with invalid param", Zap. Error(err)) // The json format Error is not a validator Error and cannot be translated. So here to do type judgment is incremented, ok: = err. (the validator. ValidationErrors) if! ok { ResponseError(c, CodeInvalidParam) } else { ResponseErrorWithMsg(c, CodeInvalidParam, RemoveTopStruct (errs.translate (trans)))} return} Err := logic.login (p) if err! Zap.l ().error ("login failed", zap.string ("username", p.user name), zap.Error(err)) if errors.Is(err, mysql.WrongPassword) { ResponseError(c, CodeInvalidPassword) } else { ResponseError(c, CodeServerBusy) } return } ResponseSuccess(c, "Login success")} func RegisterHandler(c *gin.Context) {p := new(models.paramregister) // If err := c.shouldbindjson (p); err ! = nil { zap.L().Error("RegisterHandler with invalid param", Zap. Error(err)) // The json format Error is not a validator Error and cannot be translated. So here to do type judgment is incremented, ok: = err. (the validator. ValidationErrors) if! ok { ResponseError(c, CodeInvalidParam) } else { ResponseErrorWithMsg(c, CodeInvalidParam, RemoveTopStruct (errs.translate (trans)))} return} Err := logic.Register(p) if err! = nil { zap.L().Error("register failed", zap.String("username", p.UserName), zap.Error(err)) if errors.Is(err, mysql.UserAleadyExists) { ResponseError(c, CodeUserExist) } else { ResponseError(c, CodeInvalidParam)} return ResponseSuccess(c, "register success")}Copy the code

Finally, take a look at our results:

Implement JWT authentication mode

The concept of JWT being able to look up on its own is not repeated here. Only one JWT login authentication is implemented

Package JWT import ("errors" "time" "github.com/golang-jwt/jwt") // MyClaims  { UserId int64 `json:"userId"` UserName string `json:"userName"` jwt.StandardClaims } const TokenExpireDuration = Hour * 2 var mySerect = []byte(" Wuyue is good man") // GenToken generate token func GenToken(username string, userid int64) (string, error) { c := MyClaims{ UserId: userid, UserName: username, StandardClaims: Jwt.standardclaims {ExpiresAt: time.now ().add (TokenExpireDuration).unixnano (), // Token token := jwt.NewWithClaims(jwt.SigningMethodHS256, SignedString(mySerect)} // ParseToken ParseToken func ParseToken(tokenString string) (*MyClaims, error) { var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (interface{}, error) { return mySerect, nil }) if err ! = nil {return nil, err} // Check token if token.Valid {return MC, nil} return nil, errors.New("invalid token")}Copy the code

All that remains is to return the token to the client upon successful login

Find our Logic layer:

func Login(login *models.ParamLogin) (string, error) { user := models.User{ Username: login.UserName, Password: login.Password, } if err := mysql.Login(&user); err ! = nil { return "", err } return jwt.GenToken(user.Username, user.UserId) }Copy the code

Return our token at the Controller layer:

func LoginHandler(c *gin.Context) { p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err ! = nil { zap.L().Error("LoginHandler with invalid param", Zap. Error(err)) // The json format Error is not a validator Error and cannot be translated. So here to do type judgment is incremented, ok: = err. (the validator. ValidationErrors) if! ok { ResponseError(c, CodeInvalidParam) } else { ResponseErrorWithMsg(c, CodeInvalidParam, RemoveTopStruct (errs.translate (trans)))} return} // Business processing token, err := logic.login (p) if err! Zap.l ().error ("login failed", zap.string ("username", p.user name), zap.Error(err)) if errors.Is(err, mysql.WrongPassword) { ResponseError(c, CodeInvalidPassword) } else { ResponseError(c, CodeServerBusy) } return } ResponseSuccess(c, token) }Copy the code

Finally, look at the effect:

Authentication token

R.et ("/ping", Func (context * gin. The context) {/ / here will post man simulated token auth - token token: = context. The Request. The Header. Get the if (" auth - token ") token == "" { controllers.ResponseError(context, controllers.CodeTokenIsEmpty) return } parseToken, err := jwt.ParseToken(token) if err ! = nil { controllers.ResponseError(context, controllers.CodeTokenInvalid) return } zap.L().Debug("token parese", zap.String("username", parseToken.UserName)) controllers.ResponseSuccess(context, "pong") })Copy the code