微信扫描登录,正统的做法是通过微信开放平台,申请网页接入。
由于自己已有一个认证过的微信公众号,所以尝试下能否通过公众号已有的接口做,结果证明是可以的。
上步骤分享下吧:
1. 访问“/login”路由,后台调用微信生成临时带参数的二维码接口,生成临时二维码传给前端网页,供用户扫描。
url.py
url('^login/$', LoginView.as_view(), name='login'),
view.py
class LoginView(View):
def get(self, request):
uid = uuid.uuid1()
ticket = wpManager.createShortTicket("login_{}".format(uid))["ticket"]
qrc = wpManager.getQrCode(ticket)
return render(request, 'login.html', locals())
每次刷新登录页面,都会生成一个唯一的uuid,然后生成二维码的时候,把这个uuid传入微信的scene字段中。
def createShortTicket(self, scene_str):
url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={}".format(self.getAccessToken())
data = {
"expire_seconds": 300,
"action_name": "QR_STR_SCENE",
"action_info": {
"scene": {
"scene_str": scene_str
}
}
}
ret = self.s.post(url=url, data=json.dumps(data))
return json.loads(ret.content)
2. 用户扫描后,公众号接口处理。
用户扫描二维码后,你的服务器应该能收到微信的回调。如何配置,还是要看微信的文档。
回调的格式是一段xml,我自己写了个方法转成了json。
def xml2Json(xml):
root = ET.fromstring(xml)
ret = {}
# 遍历xml文档的第二层
for child in root:
# 第二层节点的标签名称和属性
ret[child.tag] = child.text
# 遍历xml文档的第三层
for children in child:
# 第三层节点的标签名称和属性
ret[child.tag] = child.text
return ret
转换之后的json长这样:
{'ToUserName': 'gh_45fdd1acea96', 'FromUserName': 'oCpQ66MOa9KrYG2xE9mWwmuA8tgY', 'CreateTime': '1555652269', 'MsgType': 'event', 'Event': 'SCAN', 'EventKey': 'login_92a72c60-6264-11e9-b2bb-5254001e93b7', 'Ticket': 'XXXXXXXXXXXXXXXXXXXXXXXXX'}
我的处理方式是,取出这里的“EventKey”的值,看看是否有“login_”字符(因为可以用其他字符串标识其他的业务)。
如果有,就将后面的uuid作为key,用户的openid作为值,存入redis缓存,设置有效时间为5分钟。
if "login_" in data["EventKey"]:
l = data["EventKey"].split("login_")
cache.set(l[1], data["FromUserName"], 60 * 5)
response = wechat_instance.response_text("微信登录认证成功")
return HttpResponse(response)
3. 接下来要做的就简单了,在登录页面,设置ajax轮训,去后台查询缓存中是否有uuid这个key存在,如果存在,则取出openid,自动登录用户。
url.py
url('^login/checkqrc/$', LoginCheckQrcView.as_view(), name='checkqrc'),
login.html:
<script>
var count = 1;
function check() {
$.ajax({
cache: false,
type: 'get',
dataType: 'json',
url: '{% url "checkqrc" %}' + "?uid={{ uid }}",
traditional: true,
async: true,
success: function (data) {
if (data.status == "ok") {
window.location.href = "/apply";
} else {
count += 1;
if (count >= 60) {
clearInterval(checktimer);
}
}
},
error: function (xhr, textStatus) {
console.log(xhr);
console.log(textStatus);
}
});
}
check();
checktimer = setInterval(function () {
check();
}, 10000);
</script>
view.py.
class LoginCheckQrcView(View):
def get(self, request):
ret = {}
uid = request.GET.get('uid', None)
ret["status"] = "fail"
count = 1
while True:
if cache.has_key(uid):
mpid = cache.get(uid)
user, created = UserProfile.objects.get_or_create(mpid=mpid)
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
ret["status"] = "ok"
cache.delete(uid)
logger.info("{}通过微信扫描登陆成功".format(user.username))
break
else:
count += 1
if count >= 10:
break
time.sleep(1)
return HttpResponse(json.dumps(ret), content_type="application/json")
在这里我采用了长轮训的方法,每个ajax发来的请求,我都会在一个循环中等待,每隔1秒查询缓存。如果超过10秒,就返回fail.如果查询到有记录,则登录用户,并删除缓存中的key。
这样的好处是用户扫描后,网页能够马上收到响应,不会出现用户扫码了,还要等几秒网页才有反应。坏处就是如果请求登录的人数太多,会导致django后台启动的线程过多,阻塞其他接口。