前言
微信公众号,已经是目前开发者不容错过的一个通道了,像是很多餐厅都通过微信扫码点餐,很多网站都是舍弃了原本的账号密码登陆,而改用微信公众号关注登陆。针对于这个场景,今天来使用python和golang来实现一下。
流程
大致流程是这样:
- 首先我们要获取二维码和场景值
- 通过配置公众号回调地址来将场景值和获取的open_id存到redis,同时获取新用户的信息
- 然后前端通过轮询或者websocket请求登陆接口来实现。查找场景值对应的open_id,通过数据库的数据生成自定义的登陆状态。
1. 配置公众号
没有服务号没有关系,微信推出了一个测试服务号接口的地址
我们需要配置URL和Token,URL要填写你能够收到信息的服务器地址,Token你可以用某些包来生成,也可以用脸在键盘上滚几圈。
我这里用fastapi来做一下模拟,需要注意的是,文档上所需要的返回数据是text/plain而不是application/json,所以我们使用PlainTextResponse
import hashlib
from fastapi import FastAPI, Query
from fastapi.responses import JSONResponse, PlainTextResponse
app = FastAPI()
@app.get('/wechat/')
async def WeChat(
signature: str = Query(...),
timestamp: str = Query(...),
nonce: str = Query(...),
echostr: str = Query(...)
):
if not signature:
return JSONResponse("dasdfafd")
# token 微信公众平台自定义token
token = "fjklsdjfksdjkflsd"
# 将token、timestamp、nonce三个参数进行字典序排序
list1 = [token, timestamp, nonce]
list1.sort()
# 将三个参数字符串拼接成一个字符串进行sha1加密
info = "".join(list1)
sha1 = hashlib.sha1()
sha1.update(info.encode())
hashcode = sha1.hexdigest()
if hashcode == signature:
return PlainTextResponse(echostr)
else:
return ""
if __name__ == '__main__':
import uvicorn
uvicorn.run(app='test_requests:app', host="0.0.0.0", port=8010, reload=True)
点击一下就可以了。
2. 生成带参数的二维码
文档地址,需要注意的是,公众号每天获取access_token的次数是2000次,而每个access_token的过期时间是两个小时,所以我们在生产中,可以将access_token存到缓存中。
class MyWeChatOAuth:
def __init__(self):
self.app_id = 'XXX'
self.secret = 'XXX'
def get_token(self):
"""
获取access_token
"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={self.app_id}&secret={self.secret}"
ret = requests.get(url=url)
content = (ret.content).decode("utf-8")
res = json.loads(content)
return res.get("access_token")
def get_ticket(self):
url = f"https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={self.get_token()}"
scene_id = int(time.time() * 100000)
data = {
"expire_seconds": 300,
"action_name": "QR_SCENE",
"action_info": {
"scene": {
"scene_id": scene_id
}
}
}
rep = requests.post(url, json=data)
content = (rep.content).decode("utf-8")
js = json.loads(content)
ticket = js.get("ticket")
ret_url = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={}".format(ticket)
return ret_url
ret_url就是二维码的地址,此时需要将场景值scene_id返回给前端。
3. 关注/取消事件
文档地址,这个地址就要写你在配置的时候填写的url了。
判断Event,关注着的为SCAN,未关注为subscribe,取消关注为unsubscribe。这里的业务逻辑就要自己DIY了,我的选择是将获取到的用户信息入库,然后将场景值和open_id(也就是和Event同级的FromUserName)存入redis,将新关注的用户获取用户信息入库,这里就不放业务代码了。无非就是读库写库操作。
@app.post('/wechat/')
async def create_wechat(
request: Request,
):
req = await request.body()
xmlmsg = xmltodict.parse(req)
if xmlmsg["xml"]["Event"] == "SCAN":
# 已经关注的
pass
elif xmlmsg["xml"]["Event"] == "subscribe":
# 没有关注的
pass
else:
# 其他
pass
return JSONResponse("ok")
4. 获取用户信息
通过access_token和open_id来获取该用户的信息。
url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token={access_token}&openid={open_id}&lang=zh_CN"
resp = requests.get(url.format(access_token=access_token, open_id=xmlmsg["xml"]["FromUserName"]))
resp.encoding = 'utf-8'
Golang实现
由于最近在学习go语言,所以用python实现了一遍之后,还要再用go实现一遍,也为目前go的资料不多贡献一丢丢可以CV的代码。
// 获取access_token
func GetAccessToken() (string) {
var (
AppId string = "XXX"
Secret string = "XXX"
)
// 拼接url
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
AppId,
Secret)
// 初始化map
result := make(map[string]string)
ret, _ := http.Get(url)
// 读取结果
content, _ := ioutil.ReadAll(ret.Body)
// 赋值
_ = json.Unmarshal([]byte(content), &result)
return result["access_token"]
}
// 获取二维码
func GetTicket() map[string]interface{} {
result := make(map[string]interface{})
data := make(map[string]interface{})
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", GetAccessToken())
scene := fmt.Sprintf("%010v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(100000000))
scene_id,_ := strconv.ParseInt(scene, 10, 64)
data["expire_seconds"] = 300
data["action_name"] = "QR_SCENE"
action_info := make(map[string]interface{})
scene_ids := make(map[string]int64)
scene_ids["scene_id"] = scene_id
action_info["scene"] = scene_ids
data["action_info"] = action_info
req,_ := json.Marshal(data)
//json_data := bytes.NewBuffer(req)
resp, _ := http.Post(url, "application/json", bytes.NewBuffer(req))
content, _ := ioutil.ReadAll(resp.Body)
// 赋值
var res = make(map[string]string)
_ = json.Unmarshal([]byte(content), &res)
result["ticket"] = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="+res["ticket"]
result["scene_id"] = scene_id
return result
}
回调的话需要启一个http服务,我没有使用go自带的包,而是使用了web框架gin
type XmlData struct {
FromUserName string `xml:"FromUserName"`
Event string `xml:"Event"`
EventKey string `xml:"EventKey"`
}
r := gin.New()
r.POST("/wechat/", func(c *gin.Context) {
// 获取请求体
res := c.Request
// 使用结构体来接收xml的数据
var xml_data XmlData
// 转换
content, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(content))
// 赋值到结构体
_ = xml.Unmarshal([]byte(content), &xml_data)
//fmt.Println(xml_data.Event)
//fmt.Println(xml_data.FromUserName)
c.String(http.StatusOK, "hello Salmon!")
})
_ = r.Run(":8020")
然后业务代码就DIY吧,ps:吐槽一下,go写业务是真的有些麻烦,也可能是python写习惯的原因吧。
- Post link: https://www.godhearing.cn/pythongolang-shi-xian-gong-zhong-hao-sao-ma-deng-lu/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.