“This is the 26th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

preface

The front of the background function completed, today ready to write out the back-end interface on the login.

The new model

Need to do a simple background user table, do a simple function, user name, password, background display of the avatar. Then I need a token field, usually stored in Redis, for ease of use, I plan to put it directly in the background user table, and then set an expiration time. Add the ORM mapping structure to model.go and add this structure to the automatic migration code

type AdminUser struct { ID uint `gorm:"primary_key"` Name string Avatar string Password string Token string ExpirationAt  time.Time `json:"created_at"` }Copy the code
Db. AutoMigrate(&UrlList{}, &UrlType{}, &AdminUser{}) // Automatic migrationCopy the code

Starting the project automatically creates tables in the database and then new data

New Middleware method

Use this Token middleware where middleware validation is required. Query the admin_user table each time it passes through the middleware to see if the token exists and has not expired. The content carried by the token is then set into context for some interface to use. Note that middleware interception requires a C.a bout() rather than just a return. Otherwise, it will continue to walk because the method will have two returns.

import ... "main/model" ... func Token() gin.HandlerFunc { return func(context *gin.Context) { token := context.Request.Header.Get("X-Token") token_exsits, err := model.TokenInfo(token) if err ! Println(token_exsits) if len(token_exsits)! = nil {resospnseWithError(401, "Invalid request ", context) return} fpt.println (token_exsits) if len(token_exsits)! ParseInLocation("2006-01-02 15:04:05", time.Time(token_exsits[0].ExpirationAt).Format("2006-01-02 15:04:05"), If target_time.unix () <= time.now ().unix () {fmt.println (" expired ") // expired error ResospnseWithError (401, "timeout", context) return} Unix(time.now ().unix ()+7200, 0).Format("2006-01-02 15:04:05") err = model.updateTokenTime (token, now) context.Set("name", token_exsits[0].Name) context.Set("avatar", token_exsits[0].Avatar) context.Set("token", Token)} else {FMT.Println(" void ") resospnseWithError(401, "exit ", Context.next ()}} struct {Code int 'json:" Code "' // Msg string 'json:" Msg" Data interface{} 'json:" Data "' func resospnseWithError(code int, message string, C *gin.Context) {var res ResultCont res.code = Code res.msg = message c.son (200, res)}Copy the code

Create and modify routes using middleware

The login,info, and logout methods do not need the token middleware. The info and logout methods can obtain the token from the PARam of the GET method. When login is used to transfer user name and password, there is no need for middleware, and the routing code involved is as follows:

Admin: = the router Group ("/admin ") admin. Use (middleware. The Token ()) {/ / / / path mapping API: = controller. NewDyController () admin.GET("/getTypeList", controller.GetTypeList) admin.POST("/DelType", controller.DelType) admin.POST("/AddType", controller.AddType) admin.POST("/EditType", controller.EditType) admin.POST("/getUrlList", controller.GetUrlList) admin.POST("/DelUrl", controller.DelUrl) admin.POST("/AddUrl", controller.AddUrl) admin.POST("/EditUrl", controller.EditUrl) admin.POST("/user/logout", controller.Logout) } adminuser := router.Group("/admin/user") { adminuser.POST("/login", controller.Login) adminuser.GET("/info", controller.Info) }Copy the code

Creating a Controller

The controller gets the content judgment of the service method, and gets the content of the context set by the middleware to the service method, handles errors, and returns the specification

func Login(c *gin.Context) { var json request.LoginRequest if err := c.ShouldBindJSON(&json); err ! = nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } result := global.NewResult(c) data, err := service.Login(json) if err ! = nil { result.Error(5201, err.Error(), Return} result.success (data)} func Info(c *gin.Context) {token := c.de faultQuery("token", "") result := global.NewResult(c) data, err := service.Info(token) if err ! = nil { result.Error(5201, err.Error(), "Login failed ") return} result.success (data)} func Logout(c *gin.Context) {token := c.mustget ("token").(string) result := global.NewResult(c) err := service.Logout(token) if err ! = nil {result.error (5201, err.error (), "logout failed ") return} result.success (" logout failed ")}Copy the code

The validator

The content of the validator is very small, mainly the login must be a string of username and password

type LoginRequest struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}
Copy the code

The service method

The file location is service/service.go, the most important place of logic today, mainly login, make some comments, determine whether there is a token, token expiration time, according to these judgment to generate random token or directly return operation, here also introduced some new packages, Util package: public method util package

Func Login(json request.LoginRequest) (data string, err error) {// Check whether there is a user password // check whether there is a user password. If the time has not expired, Var user [] model.adminUser json.Password = util.FixMd5(json.Password + "boomXiakalaka ") user, Err = model.login (json.username, json.password) if len(user) > 0 {// succeeded if user[0].Token == "" {// Token is empty, Now := time.unix (time.now ().unix ()+7200, 0).Format("2006-01-02 15:04:05") token := util.GetRandomString(32) err = model.LoginCreateToken(json.Username, Json.Password, token, now) return token, err} else {// Token is not empty. Return target_time, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Time(user[0].ExpirationAt).Format("2006-01-02 15:04:05"), If target_time.unix () >= time.now ().unix () {return user[0].token, Nil} else {// Time expired token := util.getrandOmString (32) now := time.unix (time.now ().unix ()+7200, 0).Format("2006-01-02 15:04:05") err = model.LoginCreateToken(json.Username, json.Password, token, now) return token, Errors. New(" error ")} func Info(token string) (data interface{}, err error) {list, err := model.Info(token) return list, err } func Logout(token string) (err error) { err = model.Logout(token) return err }Copy the code

Public methods

Go file is created in the root directory. Two built-in packages are introduced, one is used for random package and time processing package. Here we can see the simplicity of Goalng. MD5 obfuscations are mainly used for password preservation. It is known that the original password stored in the database is very insecure. The following is the contents of the entire file:

Package util import ("math/rand" "time") // Generate random combinations of uppercase letters and numbers with specified digits func GetRandomString(l int) string {STR := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" bytes := []byte(str) result := []byte{} r := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < l; i++ { result = append(result, Bytes [r.i.ntn (len(bytes))])} return string(result)} //MD5 obfuscated func FixMd5(STR string) string {data := []byte(STR) has :=  md5.Sum(data) md5str := fmt.Sprintf("%x", has) return md5str }Copy the code

Model approach

I am not very familiar with GORM operation, and I use many methods to achieve various requirements, obtain user information, create tokens, and delete tokens.

func Login(name string, password string) (list []AdminUser, err error) { var user []AdminUser db.Debug().Where("name = ? and password = ?" , name, password).First(&user) return user, nil } func LoginCreateToken(name string, password string, token string, expiration_at string) (err error) { return db.Debug().Table("admin_user").Where("name = ? and password = ?" , name, password).Updates(map[string]interface{}{"token": token, "expiration_at": expiration_at}).Error } func TokenInfo(token string) (list []AdminUser, err error) { var user []AdminUser db.Debug().Where("token = ? ", token).First(&user) return user, nil } func Info(token string) (data interface{}, err error) { type Result struct { Name string Avatar string } var result Result db.Debug().Table("admin_user").Select("name, avatar").Where("token = ? ", token).Scan(&result) return result, nil } func Logout(token string) (err error) { return db.Debug().Table("admin_user").Where("token = ? ", token).Updates(map[string]interface{}{"token": ""}).Error } func UpdateTokenTime(token string, expiration_at string) (err error) { return db.Debug().Table("admin_user").Where("token = ? ", token).Updates(map[string]interface{}{"expiration_at": expiration_at}).Error } func TokenFind(token string) (status bool) { var user []AdminUser rowsAffects := db.Debug().Where("token = ? ", token).First(&user).RowsAffected if rowsAffects == 0 { return false } else { return true } }Copy the code

conclusion

The main login logic is the most complicated. For simple content system, it is generally to add data directly in the database without account registration, which is the background of our company. The password needs to be confused first and then compared to the database, so that the login is successful. Before success, the token needs to be updated for the front end to use, and the expiration time of the token needs to be dynamically updated. More code, the next update on the front end of the Settings.