自己做网站语言包怎么做,全国城建证书查询,如何入侵网站后台密码,个人网站模板flash文章目录 前置准备测试号接口配置 带参数二维码登陆获取access token获取Ticket拼装二维码Url编写接口返回二维码接收扫描带参数二维码事件编写登陆轮训接口测试页面 网页授权二维码登陆生成ticket生成授权地址获取QR码静态文件支持编写获取QR码的接口 接收重定向参数轮训登陆接… 文章目录 前置准备测试号接口配置 带参数二维码登陆获取access token获取Ticket拼装二维码Url编写接口返回二维码接收扫描带参数二维码事件编写登陆轮训接口测试页面 网页授权二维码登陆生成ticket生成授权地址获取QR码静态文件支持编写获取QR码的接口 接收重定向参数轮训登陆接口测试页面 前置准备
80/443端口能被联网访问内网穿透、云服务器测试号无需域名ip即可
申请测试号微信公众平台 (qq.com)
测试号接口配置
开始开发 / 接入指南 (qq.com) Token随便写入用于判断请求是否来自微信服务器。
URl绑定需要编写一个接口用来接收微信的信息 接收微信GET请求传来四个参数 后续事件推送也会POST到这个接口 返回参数中的Echostr参数
gin框架
wxgroup : r.Group(/wechat)
{wxgroup.Any(/message,controller.WxMessage)
}
r.Run(:80) // 80 端口启动服务func WxMessage(ctx *gin.Context) {vp : wxgo.VerifyParams{Signature: ctx.Query(signature),Echostr: ctx.Query(echostr),Timestamp: ctx.Query(timestamp),Nonce: ctx.Query(nonce),}// 判断请求是否来自微信服务器。if flag, _ : wechat.Wx.VerifySignature(*vp); flag {log.Println(vp)// 返回echostr字符串ctx.String(http.StatusOK, vp.Echostr)}
}开发者提交信息后微信服务器将发送GET请求到填写的服务器地址URL上GET请求携带参数如下表所示
参数描述signature微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。timestamp时间戳nonce随机数echostr随机字符串
开发者通过检验signature对请求进行校验下面有校验方式。若确认此次GET请求来自微信服务器请原样返回echostr参数内容则接入生效成为开发者成功否则接入失败。加密/校验流程如下
1将token、timestamp、nonce三个参数进行字典序排序
2将三个参数字符串拼接成一个字符串进行sha1加密
3开发者获得加密后的字符串可与signature对比标识该请求来源于微信
检验signature的Golang示例代码
// 签名验证
func (w *Wechat) VerifySignature(vp VerifyParams) (res bool, err error) {// 获取 token 字段 为接口配置设置的token参数token, err : w.Cfg.GetToken()if err ! nil {return false, err}// 构造匹配字段strs : []string{vp.Timestamp, vp.Nonce, token}// 按字典序排列后拼接成一个字符串sort.Strings(strs)str : strings.Join(strs, )// 对拼接后的字符串进行 SHA1 加密hash : sha1.New()hash.Write([]byte(str))hashed : fmt.Sprintf(%x, hash.Sum(nil))// 加密结果与 signature 比较if hashed ! vp.Signature {return false, errors.New(error: Signature mismatch)}return true, nil
}将接口地址http://your_ip:80/wechat/message写入配置信息中的URL即可配置成功
带参数二维码登陆
账号管理 / 生成带参数的二维码 (qq.com)
流程
获取access token通过access token与一些请求参数获取二维码ticket通过ticket换取二维码url返回给前端用户扫描二维码后事件信息推送到消息接口、将ticket放入redis前端用ticket轮训是否已经登陆
获取access token
开始开发 / 获取 Access token (qq.com) 在测试号信息下获取appID、appsecret 用http client发送请求获取access token
接口调用请求说明 https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappidAPPIDsecretAPPSECRET 参数说明
参数是否必须说明grant_type是获取access_token填写client_credentialappid是第三方用户唯一凭证secret是第三方用户唯一凭证密钥即appsecret
返回说明
正常情况下微信会返回下述JSON数据包给公众号
{access_token:ACCESS_TOKEN,expires_in:7200}参数说明
参数说明access_token获取到的凭证expires_in凭证有效时间单位秒
错误时微信会返回错误码等信息JSON数据包示例如下该示例为AppID无效错误:
{errcode:40013,errmsg:invalid appid}// 获取普通access token
func (w *Wechat) GetATReq() error {// 获取appID与appsecretappid, err : w.Cfg.GetAppid()if err ! nil {return err}appsecret, err : w.Cfg.GetAppsecret()if err ! nil {return err}// 构造请求地址// ReqUrl.ATUrl: https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappid%ssecret%s,url : fmt.Sprintf(ReqUrl.ATUrl, appid, appsecret)// 发送 GET 请求获取响应client : http.Client{}req, err : http.NewRequest(GET, url, nil)if err ! nil {return err}resp, err : client.Do(req)if err ! nil {return err}defer resp.Body.Close()// 读取响应body, err : io.ReadAll(resp.Body)if err ! nil {return err}// 如果响应结果包含错误错误码返回错误信息if strings.Contains(string(body), errcode) {return fmt.Errorf(wechat response error: %s, string(body))}// 解析字符串err json.Unmarshal(body, w.LatestAT)if err ! nil {return errors.New(json Unmarshal fail)}// 设置成功获取时间w.LatestAT.Time time.Now()return nil
}// wechat 用于保存at与配置信息
type Wechat struct {Cfg *WechatCfgLatestAT *AT
}
// access token
type AT struct {AccessToken string json:access_tokenExpiresTime int json:expires_inTime time.Time // access token获取时间
}
// 配置
type WechatCfg struct {Token stringAppid stringAppsecret stringExpiresTime int
}
// 获取 accesstoken
func (w *Wechat) GetAccessToken() (at string, err error) {w.RefreshAT()return w.LatestAT.AccessToken, nil
}
// 刷新 LatestAT
func (w *Wechat) RefreshAT() error {// 先判断上次获取的是否超时duration : time.Since(w.LatestAT.Time)durationInSeconds : int(duration.Seconds())if durationInSeconds (w.LatestAT.ExpiresTime - 600) {return nil}// 发送请求获取access tokenerr : w.GetATReq()return err
}获取Ticket
创建二维码ticket
每次创建二维码ticket需要提供一个开发者自行设定的参数scene_id分别介绍临时二维码和永久二维码的创建二维码ticket过程。
临时二维码请求说明 http请求方式: POST URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_tokenTOKEN POST数据格式json POST数据例子{“expire_seconds”: 604800, “action_name”: “QR_SCENE”, “action_info”: {“scene”: {“scene_id”: 123}}} 或者也可以使用以下POST数据创建字符串形式的二维码参数{“expire_seconds”: 604800, “action_name”: “QR_STR_SCENE”, “action_info”: {“scene”: {“scene_str”: “test”}}} 参数说明
参数说明expire_seconds该二维码有效时间以秒为单位。 最大不超过2592000即30天此字段如果不填则默认有效期为60秒。action_name二维码类型QR_SCENE为临时的整型参数值QR_STR_SCENE为临时的字符串参数值QR_LIMIT_SCENE为永久的整型参数值QR_LIMIT_STR_SCENE为永久的字符串参数值action_info二维码详细信息scene_id场景值ID临时二维码时为32位非0整型永久二维码时最大值为100000目前参数只支持1–100000scene_str场景值ID字符串形式的ID字符串类型长度限制为1到64
func (w *Wechat) GetQRTicketReq(codetype string, sceneId int) (string, error) {// 获取 access tokenat, err : w.GetAccessToken()if err ! nil {return , err}// 拼接请求地址url : fmt.Sprintf(ReqUrl.TicketUrl, at)// 构造请求数据data : QRCodeReq{ExpireSeconds: w.Cfg.GetExpiresTime(),ActionName: codetype, // QR码 类型ActionInfo: ActionInfo{Scene: Scene{SceneId: sceneId,},},}// 发送 post 请求获取响应client : http.Client{}Jsondata, err : json.Marshal(data)if err ! nil {return , err}reader : bytes.NewReader(Jsondata)req, err : http.NewRequest(POST, url, reader)if err ! nil {return , err}// 设置请求头 json格式req.Header.Set(Content-Type, application/json)resp, err : client.Do(req)if err ! nil {return , err}defer resp.Body.Close()// 读取响应body, err : io.ReadAll(resp.Body)if err ! nil {return , err}var respData QRCodeRes{}// 解析字符串err json.Unmarshal(body, respData)if err ! nil {return , errors.New(json unmarsha fail)}return respData.Ticket, nil
}拼装二维码Url
通过ticket换取二维码
获取二维码ticket后开发者可用ticket换取二维码图片。请注意本接口无须登录态即可调用。
请求说明 HTTP GET请求请使用https协议https://mp.weixin.qq.com/cgi-bin/showqrcode?ticketTICKET 提醒TICKET记得进行UrlEncode 返回说明
ticket正确情况下http 返回码是200是一张图片可以直接展示或者下载。 HTTP头示例如下 Accept-Ranges:bytes Cache-control:max-age604800 Connection:keep-alive Content-Length:28026 Content-Type:image/jpg Date:Wed, 16 Oct 2013 06:37:10 GMT Expires:Wed, 23 Oct 2013 14:37:10 0800 Server:nginx/1.4.1 错误情况下如ticket非法返回HTTP错误码404。
func (w *Wechat) GetQrImageUrl(ticket string) string {ticket url.QueryEscape(ticket) // 进行UrlEncode// ReqUrl.QRImgUrl: https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket%surl : fmt.Sprintf(ReqUrl.QRImgUrl,ticket)return url
}编写接口返回二维码
wxgroup.GET(/getloginqr,controller.GetGZQrUrl)// 获取公众号登陆二维码
func GetGZQrUrl(ctx *gin.Context) {// 获取ticketticket, _ : wechat.Wx.GetQRTicketReq(QR_STR_SCENE, 123)// 拼装二维码urlqrUrl : wechat.Wx.GetQrImageUrl(ticket)ctx.JSON(http.StatusOK, gin.H{ticket: ticket,qrUrl: qrUrl,})
}接收扫描带参数二维码事件
基础消息能力 / 接收事件推送 (qq.com)基础消息能力 / 接收事件推送 (qq.com)
用户扫描带场景值二维码时可能推送以下两种事件
如果用户还未关注公众号则用户可以关注公众号关注后微信会将带场景值关注事件推送给开发者。如果用户已经关注公众号则微信会将带场景值扫描事件推送给开发者。
推送XML数据包示例
xmlToUserName![CDATA[toUser]]/ToUserNameFromUserName![CDATA[FromUser]]/FromUserNameCreateTime123456789/CreateTimeMsgType![CDATA[event]]/MsgTypeEvent![CDATA[subscribe]]/EventEventKey![CDATA[qrscene_123123]]/EventKeyTicket![CDATA[TICKET]]/Ticket
/xml参数说明
参数描述ToUserName开发者微信号FromUserName发送方账号一个OpenIDCreateTime消息创建时间 整型MsgType消息类型eventEvent事件类型subscribe关注 / SCAN之前已关注EventKey事件KEY值qrscene_为前缀后面为二维码的参数值Ticket二维码的ticket可用来换取二维码图片
在最开始时的消息接口添加消息处理逻辑接收到扫描带参数二维码事件推送后将ticket和openid放入redis
func WxMessage(ctx *gin.Context) {vp : wxgo.VerifyParams{Signature: ctx.Query(signature),Echostr: ctx.Query(echostr),Timestamp: ctx.Query(timestamp),Nonce: ctx.Query(nonce),}if flag, _ : wechat.Wx.VerifySignature(*vp); flag {log.Println(vp)ctx.String(http.StatusOK, vp.Echostr)}// 添加消息处理逻辑uEvent : wxgo.UserEvent{}ctx.ShouldBindXML(uEvent)fmt.Println(uEvent)if uEvent.Ticket ! (uEvent.Event SCAN || uEvent.Event subscribe) {openid : uEvent.FromUserNamelog.Printf(ticket:%s, openid:%s, uEvent.Ticket, openid)// 将ticket和openid存入rediserr : redis.RedisClient.Set(uEvent.Ticket, openid, 60*time.Second).Err()if err ! nil {log.Println(err.Error())}}
}
// 用户事件
type UserEvent struct {ToUserName string xml:ToUserName // 开发者微信号FromUserName string xml:FromUserName // 发送方账号一个OpenIDCreateTime int xml:CreateTime // 消息创建时间整型MsgType string xml:MsgType // 消息类型event,Event string xml:Event // 事件类型subscribe关注, SCAN已关注EventKey string xml:EventKey // 事件KEY值qrscene_为前缀后面为二维码的参数值Ticket string xml:Ticket // 二维码的ticket可用来换取二维码图片
}编写登陆轮训接口
前端需要传递参数ticket询问是否登陆
// 登陆轮训接口
func CheckLogin(ctx *gin.Context) {ticket : ctx.Query(ticket)// 从redis中读取ticketopenid, err : redis.RedisClient.Get(ticket).Result()if err ! nil {ctx.JSON(http.StatusOK, gin.H{login: false,})} else {ctx.JSON(http.StatusOK, gin.H{login: true,openid: openid,})}
}wxgroup.GET(/checklogin,controller.CheckLogin)测试页面
修改两处请求地址
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0title登录页面/titlestyle#qr-code {width: 200px;height: 200px;}/style
/head
bodydiv idqr-code-containerimg idqr-code src# altQR Code/divh1 idlogin-status/h1h3 idlogin-user/h3script// 请求后端获取二维码图片地址function getQRCode() {// 修改请求地址fetch(http://your_ip/wechat/getloginqr) // 假设/get_qr_code是后端提供的接口地址.then(response response.json()).then(data {console.log(data.qrUrl)document.getElementById(qr-code).src data.qrUrl;// 开始轮询检查登录状态// 根据问号分割URL获取问号后面的部分var queryString data.qrUrl.split(?)[1];// 根据等号分割查询字符串获取ticket参数的值var ticket queryString.split()[1];console.log(ticket);checkLoginStatus(ticket);}).catch(error console.error(Error:, error));}// 轮询检查登录状态function checkLoginStatus(ticket) {// 修改请求地址url http://your_ip/wechat/checklogin?ticket ticketconsole.log(url)var checkLoginInterval setInterval(() {fetch(url) // 假设/check_login_status是后端提供的接口地址.then(response response.json()).then(data {console.log(data)if (data.login) {document.getElementById(login-status).innerText 登录成功;document.getElementById(login-user).innerText Openid: data.openid; clearInterval(checkLoginInterval); // 登录成功后停止轮询} }).catch(error console.error(Error:, error));}, 2000); // 每隔2秒轮询一次}// 页面加载完成后立即获取二维码window.onload function() {getQRCode();};/script
/body
/html网页授权二维码登陆
微信网页开发 / 网页授权 (qq.com)
微信公众测试号不支持PC端网页登陆但是可以模拟带参数二维码的登陆流程来实现类似的功能
流程
生成ticket作为授权地址的state参数编写接口作为授权地址都redirect_uri参数用授权地址生成QR码并将QR码与ticket返回给前端用户扫描QR码将携带code和state参数重定向到接口接口接收code和state中的ticket参数用code向微信服务器获取用户的access token和open id将ticket和openid存入redis前端用ticket轮训是否登陆
生成ticket
生成随机字符串
func GenerateRandomTicket(length int) string {rand.Seed(time.Now().UnixNano())var letters []rune(abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789)b : make([]rune, length)for i : range b {b[i] letters[rand.Intn(len(letters))]}return string(b)
}生成授权地址
在确保微信公众账号拥有授权作用域scope参数的权限的前提下已认证服务号默认拥有scope参数中的snsapi_base和snsapi_userinfo 权限引导关注者打开如下页面 https://open.weixin.qq.com/connect/oauth2/authorize?appidAPPIDredirect_uriREDIRECT_URIresponse_typecodescopeSCOPEstateSTATE#wechat_redirect 参数说明
参数是否必须说明appid是公众号的唯一标识redirect_uri是授权后重定向的回调链接地址 请使用 urlEncode 对链接进行处理response_type是返回类型请填写codescope是应用授权作用域snsapi_base 不弹出授权页面直接跳转只能获取用户openidsnsapi_userinfo 弹出授权页面可通过openid拿到昵称、性别、所在地。并且 即使在未关注的情况下只要用户授权也能获取其信息 state否重定向后会带上state参数开发者可以填写a-zA-Z0-9的参数值最多128字节#wechat_redirect是无论直接打开还是做页面302重定向时候必须带此参数forcePopup否强制此次授权需要用户弹窗确认默认为false需要注意的是若用户命中了特殊场景下的静默授权逻辑则此参数不生效
// 生成ticket
ticket : wxgo.GenerateRandomTicket(20)
// 生成授权地址
redirect_url : http://39.101.78.10/wechat/accessusercode // 微信授权后重定向地址,用于接收用户code
scope : snsapi_base //授权权限
oauthUrl : wechat.Wx.GetOauth2CodeUrl(redirect_url, scope, ticket) // 授权地址// Oauth2CodeUrl: https://open.weixin.qq.com/connect/oauth2/authorize?appid%sredirect_uri%sresponse_typecodescope%sstate%s#wechat_redirect
func (w *Wechat) GetOauth2CodeUrl(redirectUrl string, scope string, state string) string {encodeUrl : url.QueryEscape(redirectUrl)url : fmt.Sprintf(ReqUrl.Oauth2CodeUrl, w.Cfg.Appid, encodeUrl, scope, state)return url
}获取QR码
静态文件支持
qr码保存到服务器中需要联网访问到
r.Static(/static,resource)编写获取QR码的接口
wxgroup.GET(/getauthqr,controller.GetAuthQrUrl)// 获取网页授权登陆二维码
func GetAuthQrUrl(ctx *gin.Context) {// 生成ticketticket : wxgo.GenerateRandomTicket(20)// 生成授权地址redirect_url : http://your_ip/wechat/accessusercode // 微信授权后重定向地址,用于接收用户codescope : snsapi_base //授权权限oauthUrl : wechat.Wx.GetOauth2CodeUrl(redirect_url, scope, ticket)// 将授权地址生成QR码savePath : ./resource/imageerr : wxgo.GenerateQrCode(oauthUrl, savePath, ticket)if err ! nil {log.Fatal(err.Error())return}qrUrl : fmt.Sprintf(http://your_ip/static/image/%s.png, ticket)ctx.JSON(http.StatusOK, gin.H{ticket: ticket,qrUrl: qrUrl,})
}GenerateQrCode:
func GenerateQrCode(url string, savedir string, fname string) error {// github.com/skip2/go-qrcode 包qrcode, err : qrcode.New(url, qrcode.Highest)if err ! nil {return err}qrcode.DisableBorder true//保存成文件savepath : fmt.Sprintf(%s/%s.png, savedir, fname)err qrcode.WriteFile(256, savepath)return err
}接收重定向参数
如果用户同意授权页面将跳转至 redirect_uri/?codeCODEstateSTATE。 code说明code作为换取access_token的票据每次用户授权带上的code将不一样code只能使用一次5分钟未被使用自动过期。 // 网页授权接收code
func AccessUserCode(ctx *gin.Context) {code : ctx.Query(code)ticke : ctx.Query(state)// 用code获取用户access tokenuat, _ : wechat.Wx.GetUserATReq(code)// 用access token获取用户信息uInfo, _ : wechat.Wx.GetUserInfoReq(uat)log.Printf(uInfo.NickName, uInfo.Headimgurl, uInfo.City)// 将ticket和openid放入redisredis.RedisClient.Set(ticke, uInfo.OpenId, 60*time.Second)// 重定向到成功/失败页面ctx.Redirect(http.StatusTemporaryRedirect, http://your_ip/static/html/loginsucceed.html)
}轮训登陆接口
复用带参数二维码的登陆轮训接口
测试页面
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0title登录页面/titlestyle#qr-code {width: 200px;height: 200px;}/style
/head
bodydiv idqr-code-containerimg idqr-code src# altQR Code/divh1 idlogin-status/h1h3 idlogin-user/h3script// 请求后端获取二维码图片地址function getQRCode() {// 修改ipfetch(http://your_ip/wechat/getauthqr) .then(response response.json()).then(data {console.log(data.qrUrl)document.getElementById(qr-code).src data.qrUrl;// 开始轮询检查登录状态var ticket data.ticket;console.log(ticket);checkLoginStatus(ticket);}).catch(error console.error(Error:, error));}// 轮询检查登录状态function checkLoginStatus(ticket) {// 修改ipurl http://your_ip/wechat/checklogin?ticket ticketconsole.log(url)var checkLoginInterval setInterval(() {fetch(url) // 假设/check_login_status是后端提供的接口地址.then(response response.json()).then(data {console.log(data)if (data.login) {document.getElementById(login-status).innerText 登录成功;document.getElementById(login-user).innerText Openid: data.openid; clearInterval(checkLoginInterval); // 登录成功后停止轮询} }).catch(error console.error(Error:, error));}, 2000); // 每隔2秒轮询一次}// 页面加载完成后立即获取二维码window.onload function() {getQRCode();};/script
/body
/html