网站利用扫码关注公众号实现一键登录(上)

网站利用扫码关注公众号实现一键登录

      最近接了一个单子,其中有个需求是需要扫码关注公众号实现网站登陆,由于篇幅过长,可能需要分成两篇跟大家分享,今天先分享微信公众号部分的对接,下一篇再分享关注后如何实现登陆。

      我这里项目是用go语言开发,这里就按照go语言进行演示,大家可以参考后根据自己实际情况进行开发。

      一、对接微信公众号accessToken

//缓存key名称
const wxPublicAccessTokenKey = "WxPublicAccessToken"

// 获取公众号accessToken
func WxPublicGetAccessToken(appId, secret string) (int, string) {
	//判断是否存在缓存
	keyExit, _ := model.Exist(wxPublicAccessTokenKey)
	if keyExit != false {
		//提取数据
		accessTokenString := ""
		_, accessToken := model.StringGet(wxPublicAccessTokenKey, accessTokenString)
		//返回
		return 200, accessToken
	} else {
		//请求url
		apiUrl := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + secret
		//设置请求方式
		req, _ := http.NewRequest("GET", apiUrl, nil)
		req.Header.Add("cache-control", "no-cache")
		//执行请求
		res, _ := http.DefaultClient.Do(req)
		//关闭链接
		defer res.Body.Close()
		//提取数据
		result, err := io.ReadAll(res.Body)
		if err != nil {
			return 404, "结构解析异常"
		}
		//处理json
		var resultList interface{}
		errResult := json.Unmarshal(result, &resultList)
		if errResult != nil {
			return 404, "结构转化失败"
		}
		//提取数据
		resultDataList, _ := resultList.(map[string]interface{})
		//判断是否存在错误信息
		errMsg, errMsgErr := resultDataList["errmsg"].(string)
		if errMsgErr {
			return 404, errMsg
		}
		//提取参数
		accessToken := resultDataList["access_token"].(string)
		expiresIn := resultDataList["expires_in"].(float64)
		//设置过期时间
		expiresInOutTime := int(expiresIn) - 60*20
		//设置缓存
		err = model.StringSet(wxPublicAccessTokenKey, accessToken, expiresInOutTime)
		if err != nil {
			return 404, "设置缓存失败"
		}
		//返回
		return 200, accessToken
	}
}

这里我是将accessToken放在redis缓存里面,优先查询是否存在缓存,如果不存在则再调取接口进行获取,然后再次存放在缓存里,这里可以避免过度调用接口,redis部分我就不贴出来了,仅供大家参考。

      二、生成微信公众号二维码

// 获取公众号临时二维码
func WxPublicCreateQrCode(accessToken, sceneStr string) (int, string) {
   //请求地址
   qrCodeUrl := "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" 
   + accessToken
   //组合动作参数
   var actionInfoParams = map[string]interface{}{
      "scene": map[string]interface{}{
         "scene_str": sceneStr,
      },
   }
   //组合参数
   var param = map[string]interface{}{
      "expire_seconds": 300,
      "action_name":    "QR_STR_SCENE",
      "action_info":    actionInfoParams,
   }
   jsonStr, _ := json.Marshal(param)
   //转化数据
   postData := bytes.NewBuffer(jsonStr)
   //发起请求
   response, err := http.Post(qrCodeUrl, "application/json;charset=utf-8", postData)
   if err != nil {
      return 404, "请求失败"
   }
   //关闭链接
   defer response.Body.Close()
   result, err := io.ReadAll(response.Body)
   if err != nil {
      return 404, "结构解析异常"
   }
   //处理json
   var resultList interface{}
   errResult := json.Unmarshal(result, &resultList)
   if errResult != nil {
      return 404, "结构转化失败"
   }
   //提取数据
   resultDataList, _ := resultList.(map[string]interface{})
   //判断是否存在错误信息
   errMsg, errMsgErr := resultDataList["errmsg"].(string)
   if errMsgErr {
      return 404, errMsg
   }
   //获取ticket
   ticket := resultDataList["ticket"].(string)
   //返回
   return 200, ticket
}

这里我们调取公众号临时二维码接口生成二维码,我们需要生成一个唯一的ID值生成公众号场景值,这样在用户扫码进入公众号的时候,会将该值携带请求到我们设置回调地址,方便我们区分。

     三、处理公众号事件回调

     我们还需要开发一个回调接口来处理公众号回调事件,这里我们需要接收公众号用户扫码跟关注事件处理,然后将上一步生成的唯一ID跟公众号用户ID进行绑定,这里我们需要处理GET跟POST请求,GET请求只会在确认回调地址的时候触发,公众号事件请求则是通过POST请求进行触发,代码如下
     回调地址确认接口

/**
微信公众号回调处理【GET:首次确认】
 */
func (c *WxLoginController) GetPublicCallBack() string {
   //获取用户参数
   signature := params.FormValueDefault(c.Ctx, "signature", "") //密钥
   timestamp := params.FormValueDefault(c.Ctx, "timestamp", "") //时间戳
   nonce := params.FormValueDefault(c.Ctx, "nonce", "")         //随机数
   echostr := params.FormValueDefault(c.Ctx, "echostr", "")     //随机字符串
   //获取配置
   publicToken := "公众号回调地址设置的token"
   //将token、timestamp、nonce三个参数进行字典序排序
   var tempArray = []string{publicToken, timestamp, nonce}
   sort.Strings(tempArray)
   //将三个参数字符串拼接成一个字符串进行sha1加密
   var sha1String string
   for _, v := range tempArray {
      sha1String += v
   }
   h := sha1.New()
   h.Write([]byte(sha1String))
   sha1String = hex.EncodeToString(h.Sum([]byte("")))
   //获得加密后的字符串可与signature对比
   if sha1String == signature {
      //输出解密成功
      return echostr
   } else {
      //返回
      return "false"
   }
}

这里我们验证成功后,直接将echostr输出即可完成验证,token需要跟公众号回调地址配置上面一致

1.png

      事件回调处理接口
      我们还需要开发一个接收POST请求的接口,用于处理事件

// 定义一个结构体用于解析微信传来的消息
type WxCallbackMessage struct {
	ToUserName   string `xml:"ToUserName"`
	FromUserName string `xml:"FromUserName"`
	CreateTime   int    `xml:"CreateTime"`
	MsgType      string `xml:"MsgType"`
	Event        string `xml:"Event"`
	EventKey     string `xml:"EventKey"`
	Ticket       string `xml:"Ticket"`
}

/**
微信公众号回调处理【POST:事件推送】
 */
func (c *WxLoginController) PostPublicCallBack() string {
	//获取用户参数
	signature := params.FormValueDefault(c.Ctx, "signature", "") //密钥
	timestamp := params.FormValueDefault(c.Ctx, "timestamp", "") //时间戳
	nonce := params.FormValueDefault(c.Ctx, "nonce", "")         //随机数
	//获取配置
	publicToken := "微信公众号后台配置的Token"
	//对参数进行字典序排序并进行加密验证
	var tempArray = []string{publicToken, timestamp, nonce}
	sort.Strings(tempArray)
	var sha1String string
	for _, v := range tempArray {
		sha1String += v
	}
	// 使用 SHA1 对字符串进行加密
	h := sha1.New()
	h.Write([]byte(sha1String))
	sha1String = hex.EncodeToString(h.Sum(nil))
	// 验证签名
	if sha1String == signature {
		// 微信消息的处理:解析传递过来的 XML 消息
		var message WxCallbackMessage
		if err := xml.NewDecoder(c.Ctx.Request().Body).Decode(&message); err != nil {
			return "Failed to parse message"
		}
		println("事件", message.Event)
		println("动作", message.EventKey)
		//获取基本信息
		fromUserName := message.FromUserName //用户openId
		eventKey := message.EventKey         //额外参数
		eventEvent := message.Event          //事件
		// 处理关注事件
		if eventEvent == "subscribe" {
			//判断获取令牌
			if len(eventKey) > 0 && strings.Contains(eventKey, "qrscene") {
				loginToken := strings.Replace(eventKey, "qrscene_", "", -1)
				if loginToken != "" {
					//创建一条公众号+令牌登录信息
					........创建一条公众号openId、sceneStr、状态关联记录
				}
			}
		}
		// 处理扫码进入公众号
		if eventEvent == "SCAN" {
			//判断获取令牌
			if len(eventKey) > 0 && len(eventKey) == 32 {
				loginToken := eventKey
				if loginToken != "" {
					//创建一条公众号+令牌登录信息
					........创建一条公众号openId、sceneStr、状态关联记录
				}
			}
		}
		// 处理取消关注事件
		if eventEvent == "unsubscribe" {

		}
		// 默认返回消息
		return "Success"
	}
	// 不是微信的请求,返回错误提示
	return "false"
}

上面我们需要处理用户关注事件以及用户扫描进入公众号事件,在接收到用户openId以及sceneStr值之后,我们需要创建一条记录,用来关联两者并且还需要有一个状态值,如果扫码关注之后,将状态值更改为有效,用户端在登陆之后,则改成失效状态。

     四、生成微信公众号二维码接口

     完成基本开发之后,我们再开发一个生成二维码接口返回给前端并且需要返回一个唯一ID给前端,前端通过该唯一ID查询用户是否已经扫码登陆

/**
微信公众号生成二维码
 */
func (c *WxLoginController) PostPublicGetQrCode() *web.JsonResult {
   // 生成唯一编码
   uuidStr := uuid.New().String()
   sceneStr := uuidStr[:32]
   //生成公众号关注二维码
   apiCode, apiResult := WechatService.WechatGetPublicQrCode(sceneStr)
   if apiCode != 200 {
      return web.JsonErrorMsg(apiResult)
   }
   //组合生成连接
   qrCode := "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + apiResult
   //返回数据
   return web.NewEmptyRspBuilder().Put("qrCode", qrCode).Put("sceneStr", sceneStr).JsonResult()
}

这个接口我们会生成一个二维码地址以及对应的唯一ID返回给前端。    

     我们便完成了对微信公众号部分的对接,下一篇跟大家分享用户关注后如何通知前端,完成一键登录。


0条评论

发表评论