1、二维码页面,请求接口获取 logincode 和二维码地址页面保存 logincode ,此logincode将会成为登录标识符2 、二维码接口 生成logincode,获取二维码地址返回给二维码页面3、微信扫描二维码,微信会向服务器以post方式发送消息 ,此接口通过微信公众平台启用服务器配置的接口 配置接口获取数据中有 FromUserName 这就是openid;EventKey 是带qrscene_前缀的loginCode通过loginCode := strings.TrimPrefix(msg.EventKey, "qrscene_") 获取loginCode通过openid,查询、创建用户,将loginCode以登录标识保存起来const ( token = "tianwanggaidihu" // 替换为自己在微信公众号设置的 Token,配置服务器时自定义填写的)type AccessTokenResponse struct { AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"`}type QRCodeTicketResponse struct { Ticket string `json:"ticket"` ExpireSeconds int `json:"expire_seconds"` URL string `json:"url"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"`}// 添加消息结构体 推送的消息结构体type TextMessage struct { ToUser string `json:"touser"` MsgType string `json:"msgtype"` Text struct { Content string `json:"content"` } `json:"text"`}接口1、//get请求,用于配置时的验证,验证通过后就没用了func (ct *WeixinController) Fromweixinplat(ctx *gin.Context) { signature := ctx.Query("signature") timestamp := ctx.Query("timestamp") nonce := ctx.Query("nonce") echostr := ctx.Query("echostr") // 1. 将 token、timestamp、nonce 三个参数进行字典序排序 strs := sort.StringSlice{token, timestamp, nonce} sort.Strings(strs) // 2. 将三个参数字符串拼接成一个字符串进行 sha1 加密 str := strings.Join(strs, "") h := sha1.New() h.Write([]byte(str)) sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) // 3. 将加密后的字符串与 signature 进行对比 return sha1Sum == signature if sha1Sum == signature { ctx.String(200, echostr) } else { ctx.String(403, "验证失败") } return}接口22.1、// 展现二维码的页面请求的接口:生成登录二维码 func (ct *WeixinController) GenerateLoginQRCode(ctx *gin.Context) { backData := Tools.BackData{} // 生成唯一登录码 loginCode := fmt.Sprintf("%d", time.Now().UnixNano()) // 获取access_token accessToken, err := ct.getAccessToken() if err != nil { ctx.JSON(500, gin.H{"error": "获取access_token失败"}) return } // 获取二维码ticket ticket, err := ct.getQRCodeTicket(accessToken, loginCode) if err != nil { ctx.JSON(500, gin.H{"error": "获取二维码ticket失败"}) return } rdb := Tools.GetRedis() // 存储登录码(设置5分钟过期) err = rdb.Set("login:"+loginCode, "pending", 5*time.Minute).Err() if err != nil { ctx.JSON(500, gin.H{"error": "生成登录码失败"}) return } // 返回登录码和二维码链接 // 注意:这里需要替换为你的公众号二维码链接 qrcodeURL := fmt.Sprintf("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s&scene=%s", ticket, loginCode) ctx.JSON(200, gin.H{ "code": loginCode, //页面存储的 "qrcode_url": qrcodeURL, //将qrcodeRRL 放到img src属性 }) }2.2、// 获取二维码ticketfunc (ct *WeixinController) getQRCodeTicket(accessToken, sceneStr string) (string, error) { url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", accessToken) // 构造请求数据 data := map[string]interface{}{ "expire_seconds": 604800, // 7天有效期 "action_name": "QR_STR_SCENE", "action_info": map[string]interface{}{ "scene": map[string]string{ "scene_str": sceneStr, }, }, } jsonData, err := json.Marshal(data) if err != nil { return "", err } resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return "", err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } var ticketResp QRCodeTicketResponse if err := json.Unmarshal(body, &ticketResp); err != nil { return "", err } if ticketResp.ErrCode != 0 { return "", fmt.Errorf("获取二维码ticket失败:%s", ticketResp.ErrMsg) } return ticketResp.Ticket, nil}接口3 // post 处理微信消息接口 微信扫码后,服务器被微信调用的接口// 微信消息结构体 type WxMessage struct { XMLName xml.Name `xml:"xml"` ToUserName string `xml:"ToUserName"` FromUserName string `xml:"FromUserName"` //卧槽这个就是openid CreateTime int64 `xml:"CreateTime"` MsgType string `xml:"MsgType"` Event string `xml:"Event"` //值有subscribe SCAN 暂时不知道啥意思,不过不影响逻辑 EventKey string `xml:"EventKey"`}3.1、func (ct *WeixinController) HandleWxMessage(ctx *gin.Context) { body, err := ioutil.ReadAll(ctx.Request.Body) if err != nil { ctx.String(400, "读取消息失败") return } var msg WxMessage if err := xml.Unmarshal(body, &msg); err != nil { ctx.String(400, "解析消息失败") return } returnmsg := "登录失败" // 处理关注事件 if msg.MsgType == "event" && msg.FromUserName != "" { // 检查是否包含场景值(二维码参数) if msg.EventKey != "" { loginCode := strings.TrimPrefix(msg.EventKey, "qrscene_") openid := msg.FromUserName /** * 1、查询三方库里是否有该用户, 如果有,直接保存登录状态 如果没有,创建账号,保存三方库 ,再保存登录状态 将loginCode作为登录标识符保存起来,便于,前端页面通过保存的loginCode 获取登录信息 */ //给扫码用户发送信息 ct.sendMessage(openid, "登录成功") } } ctx.String(200, "success")}3.2、// 向用户推送消息func (ct *WeixinController) sendMessage(openID, content string) error { // 获取access_token accessToken, err := ct.getAccessToken() if err != nil { return err } message := TextMessage{ ToUser: openID, MsgType: "text", } message.Text.Content = content jsonData, err := json.Marshal(message) if err != nil { return err } url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s", accessToken) resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return err } defer resp.Body.Close() return nil}3.3、// 获取访问令牌func (ct *WeixinController) getAccessToken() (string, error) { appID := "微信公众号appid" appSecret := "微信公众号app_secret" url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appID, appSecret) resp, err := http.Get(url) if err != nil { return "", err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } var tokenResp AccessTokenResponse if err := json.Unmarshal(body, &tokenResp); err != nil { return "", err } if tokenResp.ErrCode != 0 { return "", fmt.Errorf("获取access_token失败:%s", tokenResp.ErrMsg) } return tokenResp.AccessToken, nil}