服务端扫码登录接口
使用 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