Gin 实现二维码扫描登录接口 | Go主题月

2,043 阅读2分钟

服务端扫码登录接口

使用 gin web 框架

moose-go 基础之上开发

服务端提供 API

  • 生成二维码标识
  • 前端轮询
  • 扫描确认
type QRCodeController struct {
}

func (qrc *QRCodeController) RegisterRouter(app *gin.Engine) {
	group := app.Group("/api/v1/qrcode/")
	group.GET("/get", qrc.GetQRCode)
	group.GET("/ask", qrc.AskQRCode)
	group.POST("/sanlogin", qrc.ScanLogin)
}

func (qrc *QRCodeController) GetQRCode(c *gin.Context) {
	qrCodeService := service.QRCodeService{}
	qrCodeInfo := qrCodeService.GenerateQRCode(c)
	common.Success(c, qrCodeInfo)
}

func (qrc *QRCodeController) AskQRCode(c *gin.Context) {
	qrCodeService := service.QRCodeService{}
	status := qrCodeService.AskQRCode(c)
	common.Success(c, status)
}

func (qrc *QRCodeController) ScanLogin(c *gin.Context) {
	qrCodeService := service.QRCodeService{}
	qrCodeService.ScanLogin(c)
	common.Success(c, 1)
}

生成二维码标识

func (qrs *QRCodeService) GenerateQRCode(c *gin.Context) *model.QRCodeInfo {
	mTicket := uuid.NewV4().String()
	qrCodeUrl := fmt.Sprintf("http://192.168.1.100:7000/api/v1/qrcode/sanlogin?m_ticket=%s", mTicket)

	// result, err := _redisHelper.SetNX(ctx, constant.MOOSE_TICKET, mTicket, 3*time.Minute).Result()
	redisHelper := engine.GetRedisHelper()
	ticketKey := fmt.Sprintf(constant.MOOSE_SCAN_TICKET, mTicket)
	authInfo := &model.AuthInfo{Token: "", Status: 0}
	// 设置过期时间,三分钟
	_, err := redisHelper.Set(ctx, ticketKey, authInfo, 3*time.Minute).Result()
	if err != nil {
		log.Println(err)
		panic(api.QRCodeGetFailErr)
	}

	c.SetCookie("m_ticket", mTicket, 3*60, "/", "localhost", true, true)

	qrCodeInfo := &model.QRCodeInfo{
		CodeUrl: qrCodeUrl,
	}
	return qrCodeInfo
}
  • 生成标识使用 uuid 方式
  • AuthInfo 结构体,保存授权信息,QRCodeInfo 接口体封装 二维码信息(标识)
  • 生成的标识保存到 redis 中,设置过期时间
  • 前端请求,设置 Cookie 返回,在 ask 接口中,直接从 cookis 中获取 m_ticket 标识

AuthInfo 结构体

type AuthInfo struct {
	Token  string `json:"token"`
	Status int    `json:"status"`
}

func (u *AuthInfo) MarshalBinary() ([]byte, error) {
	return json.Marshal(u)
}
  • 实现 MarshalBinary 序列化二进制,报错到 redis 中

QRCodeInfo 结构体

type QRCodeInfo struct {
	CodeUrl string `json:"codeUrl"`
}
  • CodeUrl 前端获取到这个字段进行二维码信息渲染

扫描确认

func (qrs *QRCodeService) ScanLogin(c *gin.Context) {
	mTicket := c.Query("m_ticket")
	// 校验 m_ticket
	if mTicket == "" {
		panic(api.QRCodeRetryErr)
	}

	redisHelper := engine.GetRedisHelper()
	ticketKey := fmt.Sprintf(constant.MOOSE_SCAN_TICKET, mTicket)
	result, err := redisHelper.Get(ctx, ticketKey).Result()

	if err != nil {
		log.Println(err)
		panic(api.QRCodeRetryErr)
	}

	// 检查 ticket
	checkTicket(result)

	var authInfo model.AuthInfo
	err = json.Unmarshal([]byte(result), &authInfo)
	if err != nil {
		log.Println(err)
		panic(api.QRCodeRetryErr)
	}

	authInfo.Status = 1

	mToken := c.Query("token")
	// if token valid success
	if mToken != "" {
		authInfo.Token = mToken
	}

	_, err = redisHelper.Set(ctx, ticketKey, &authInfo, 3*time.Minute).Result()
	if err != nil {
		log.Println(err)
		panic(api.QRCodeGetFailErr)
	}
	// 设置过期时间,三分钟
	// redisHelper.Expire(ctx, ticketKey, 3*time.Minute).Result()
	log.Printf("key %s result %s", ticketKey, result)
}
  • 需要登录,校验 token 是否合法

  • 判断 m_ticket 是否存在

  • 提供给 app 调用

    • app 必须是登录状态
  • app 扫描之后,携带 token 校验是否合法

  • app 扫码获取到标识,上传一次扫描状态

  • app 弹出确认授权登录,携带 app 端 token 一起请求扫描接口

问题

  • app 是否需要上传一次正在扫描中状态?
  • pc 、app 登录授权 token 同一个?还是获取到 app token,在去请求 pc 授权接口,回传给 pc?

轮询

func (qrs *QRCodeService) AskQRCode(c *gin.Context) *model.AuthInfo {
	mTicket, err := c.Cookie("m_ticket")
	log.Printf("the m_ticket %s", mTicket)

	if err != nil {
		log.Println(err)
		panic(api.QRCodeRetryErr)
	}

	redisHelper := engine.GetRedisHelper()
	ticketKey := fmt.Sprintf(constant.MOOSE_SCAN_TICKET, mTicket)
	result, err := redisHelper.Get(ctx, ticketKey).Result()

	if err != nil {
		panic(api.QRCodeRetryErr)
	}

	// 检查 ticket
	checkTicket(result)

	var authInfo model.AuthInfo
	json.Unmarshal([]byte(result), &authInfo)

	// 扫描 token 不为空,返回之后,设置过期,在 redis Expire
	if authInfo.Token != "" {
		redisHelper.Expire(ctx, ticketKey, 0)
	}

	log.Printf("ticketKey %s result %s ", ticketKey, result)
	return &authInfo
}
  • 在轮询接口中获取设置的 cookis 信息,二维码标识(m_ticket)
  • 检查 m_ticket 合法性
  • 判断授权 token 是否回传了, token 不为空,在返回给前端授权信息之后,把 redis 当前 ticketKey 设置过期时间为 0(移除)

java 版本实现

源码 moose