从零开始摸索VUE,配合Golang搭建导航网站(二十六.vue-admin-template完善用户登录后台接口)

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战

前言

前面把后台功能完成了,今天准备把后端关于登录的接口写出来。

新建模型

需要做一个简单的后台用户表,做一个最简单的功能,用户名,密码,后台显示的头像。然后需要一个token字段,一般token存放在redis里面,为了简单易用,我准备直接放在后台用户表里面,然后设置一个过期时间。 在model.go里面加上ORM映射的结构体,在自动迁移的代码里加上这个结构体

type AdminUser struct {
	ID           uint `gorm:"primary_key"`
	Name         string
	Avatar       string
	Password     string
	Token        string
	ExpirationAt time.Time `json:"created_at"`
}
复制代码
db.AutoMigrate(&UrlList{}, &UrlType{}, &AdminUser{}) //自动迁移
复制代码

启动项目自动会在数据库创建数据表,然后新建数据

image.png

新建中间件方法

在需要中间件验证的地方使用这个Token中间件。每次经过中间件的时候查询admin_user这张表是否存在这个token,且没有过期。然后把token携带的内容设置到上下文提供给某些接口使用。这里需要注意中间件的拦截需要使用c.about()方法不能只用return。不然还是会继续走路由方法会有两个return。

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 != nil {
			resospnseWithError(401, "非法请求", context)
			return
		}
		fmt.Println(token_exsits)
		if len(token_exsits) != 0 {
			//先做时间判断
			target_time, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Time(token_exsits[0].ExpirationAt).Format("2006-01-02 15:04:05"), time.Local) //需要加上time.Local不然会自动加八小时
			if target_time.Unix() <= time.Now().Unix() {
				fmt.Println("过期报错")
				//过期报错
				resospnseWithError(401, "timeout", context)
				return
			}
			//token没过期,更新到期时间
			now := time.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("没了")
			resospnseWithError(401, "已退出", context)
			return
		}

		context.Next()
	}
}

type ResultCont 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.JSON(200, res) //前端返回也要返回200才能拦截
	c.Abort()
}


复制代码

新建修改路由,使用中间件

主要有三个方法login,infologout三个方法都不需要走token中间件,infologout和都是从get方法的param中获取token。login用于传递用户名密码就不需要中间件,涉及到的路由代码如下:

	admin := router.Group("/admin")
	admin.Use(middleware.Token())
	{
		// 路径映射
		// 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)
	}
复制代码

新建控制器

控制器获取服务方法的内容判断,还有获取中间件设置的上下文的内容交给服务方法,处理错误,规范返回

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.DefaultQuery("token", "")
	result := global.NewResult(c)
	data, err := service.Info(token)
	if err != nil {
		result.Error(5201, err.Error(), "登录失败")
		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(), "退出登录失败")
		return
	}
	result.Success("退出登录成功")
}
复制代码

验证器

验证器的内容很少,主要是登录必须要字符串的用户名密码

type LoginRequest struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}
复制代码

服务方法

文件位置是service/service.go,今天逻辑最重要的地方,主要是登录,自己做了一些注释,判断有没有token,token过期时间,根据这些判断进行生成随机token或者直接返回操作,这里还引入了一些新的包,专门存放自己写的公共方法,util包:

func Login(json request.LoginRequest) (data string, err error) {
	//判断有没有这个用户密码
	//没有报错,有了就判断token有没有,没有就创建返回,有就判断时间,时间没过期就返回,过期了就重新生成返回
	var user []model.AdminUser
        json.Password = util.FixMd5(json.Password + "boomxiakalaka")
	user, err = model.Login(json.Username, json.Password)
	if len(user) > 0 {
		//成功
		if user[0].Token == "" {
			//token为空,创建更新返回
			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不为空 ,判断时间,时间不过期直接返回
			target_time, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Time(user[0].ExpirationAt).Format("2006-01-02 15:04:05"), time.Local) //需要加上time.Local不然会自动加八小时
			if target_time.Unix() >= time.Now().Unix() {
				return user[0].Token, nil
			} else {
				//时间过期
				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, err
			}
		}
	}
	return "登陆失败", errors.New("登陆失败")
}

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
}
复制代码

公共方法

主要是生成随机的32未字符串,MD5混淆,在根目录下新建util/util.go文件,引入了两个内置包,一个是用于随机的包,和时间处理的包,这里就可以看到goalng的简陋了,随机竟然还需要获取时间来做一个随机种子来做成真正的随机;MD5混淆主要是用于密码保存,总所周知密码原文直接存放在数据库是非常不安全的,以下是整个文件内容:

package util

import (
	"math/rand"
	"time"
)

// 随机生成指定位数的大写字母和数字的组合
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.Intn(len(bytes))])
	}
	return string(result)
}

//MD5混淆
func FixMd5(str string) string {
	data := []byte(str)
	has := md5.Sum(data)
	md5str := fmt.Sprintf("%x", has)
	return md5str
}

复制代码

模型方法

对于GORM操作不是很熟悉,用了很多方法实现了各种需求,获取用户信息,创建token,删除token。

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
	}
}

复制代码

总结

主要登录逻辑最为复杂,对于简单的内容系统,一般是直接在数据库添加数据,不做账户注册,我们公司后台就是。先需要把密码混淆后对数据库对比,有了就登录成功了,成功之前还需要更新token给前端使用,还需要动态更新设置token的到期时间。代码比较多,下篇更新前端关于的设置。

Guess you like

Origin juejin.im/post/7034883872290504741