Last active
November 4, 2019 21:33
-
-
Save cloverstd/4311d15f7e4a6c50b37b to your computer and use it in GitHub Desktop.
微信二维码扫描登录
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# encoding: utf-8 | |
import tornado.ioloop | |
import tornado.httpclient | |
import tornado.web | |
import tornado.gen | |
import json | |
import tornado.websocket | |
import Queue | |
import uuid | |
import copy | |
from wechatpy import parse_message, create_reply | |
from wechatpy.utils import check_signature | |
from wechatpy.exceptions import InvalidSignatureException | |
from wechatpy import WeChatClient | |
TOKEN = 'token' | |
app_id = 'YOUR APPID' | |
app_secret = 'YOUR APPSECRET' | |
client = WeChatClient(app_id, app_secret) | |
#print client.fetch_access_token() | |
access_token = 'F6jLHXNGMSv_OfJlYSU6ToB5SyArwaskqu01Rbt49uiCTs5xJ1UmDWGagasHYjOqkw58juL76eoGXz-d2hgPAQ1Vv_mqiBC4tv9x7dosfLY' | |
clients = list() | |
users = list() | |
q = Queue.LifoQueue(1000) | |
for i in xrange(1000): | |
q.put(i) | |
users.append(dict(username=str(i), | |
openid=None)) | |
class WeChat(object): | |
def __init__(self, token): | |
self.base_api_url = 'https://api.weixin.qq.com/cgi-bin' | |
self.create_ticket_url = self.base_api_url + '/qrcode/create' | |
self.show_qrcode_url = 'https://mp.weixin.qq.com/cgi-bin/showqrcode' | |
self.http_client = tornado.httpclient.AsyncHTTPClient() | |
self.access_token = token | |
@tornado.gen.coroutine | |
def get_ticket(self, expires=1800, scene_id=1): | |
body = { | |
"expire_seconds": expires, | |
"action_name": "QR_LIMIT_STR_SCENE", | |
"action_info": { | |
"scene": { | |
"scene_str": str(scene_id) | |
} | |
} | |
} | |
url = self.create_ticket_url + '?access_token=' + self.access_token | |
request = tornado.httpclient.HTTPRequest(url=url, | |
method='POST', | |
body=json.dumps(body)) | |
response = yield self.http_client.fetch(request) | |
response = json.loads(response.body) | |
raise tornado.gen.Return(response) | |
@tornado.gen.coroutine | |
def get_qr_url(self, expires=1800, scene_id=1): | |
data = yield self.get_ticket(expires, scene_id) | |
print data | |
url = self.show_qrcode_url + '?ticket=' + data['ticket'] | |
raise tornado.gen.Return(url) | |
class WebSocketHandler(tornado.websocket.WebSocketHandler): | |
@tornado.gen.coroutine | |
def open(self): | |
print 'open' | |
wechat = WeChat(access_token) | |
qid = q.get() | |
qr = yield wechat.get_ticket(180, qid) | |
c = { | |
'client': self, | |
'qid': qid, | |
'ticket': qr['ticket'], | |
'uuid': str(uuid.uuid4()), | |
'expires': 180, | |
} | |
clients.append(c) | |
d = copy.copy(c) | |
del d['qid'], d['client'] | |
self.write_message(json.dumps(dict(type='qr', data=d))) | |
@tornado.gen.coroutine | |
def on_message(self, message): | |
msg = None | |
try: | |
msg = json.loads(message) | |
except: | |
msg = message | |
pass | |
print msg | |
def on_close(self): | |
for c in clients: | |
if c['client'] == self: | |
q.put(c['qid']) | |
clients.remove(c) | |
print 'WebSocket closed' | |
class IndexHandler(tornado.web.RequestHandler): | |
def get(self): | |
self.render('index.html') | |
def post(self): | |
username = self.get_argument('username') | |
for user in users: | |
if user['username'] == username: | |
print 'ok' | |
self.set_cookie('user', username) | |
return self.redirect('/user', 302) | |
self.redirect('/', 302) | |
class UserCenterHandler(tornado.web.RequestHandler): | |
def get_current_user(self): | |
user = self.get_cookie('user') | |
for _user in users: | |
if _user['username'] == user: | |
return _user | |
return user | |
@tornado.web.authenticated | |
def get(self): | |
self.render('usercenter.html', user=self.current_user) | |
class UserBindHandler(tornado.web.RequestHandler): | |
def post(self): | |
print self.request.body | |
print self.request.arguments | |
username = self.get_argument('username') | |
openid = self.get_argument('openid') | |
for user in users: | |
if user['username'] == username: | |
user['openid'] = openid | |
data = { | |
'status': True | |
} | |
self.set_cookie('user', username) | |
break | |
else: | |
data = { | |
'status': False, | |
'err': '用户不存在' | |
} | |
self.set_status(403) | |
self.set_header('content-type', 'application/json') | |
for user in users: | |
if user['openid']: | |
print user | |
self.write(json.dumps(data)) | |
class LogoutHandler(tornado.web.RequestHandler): | |
def get(self): | |
self.clear_all_cookies() | |
self.redirect('/', 302) | |
class WeChatHandler(tornado.web.RequestHandler): | |
def get(self): | |
signature = self.get_argument('signature', '') | |
timestamp = self.get_argument('timestamp', '') | |
nonce = self.get_argument('nonce', '') | |
echo_str = self.get_argument('echostr', '') | |
try: | |
check_signature(TOKEN, signature, timestamp, nonce) | |
except InvalidSignatureException: | |
self.set_status(403) | |
return self.write('hi') | |
self.write(echo_str) | |
def post(self): | |
signature = self.get_argument('signature', '') | |
timestamp = self.get_argument('timestamp', '') | |
nonce = self.get_argument('nonce', '') | |
try: | |
check_signature(TOKEN, signature, timestamp, nonce) | |
except InvalidSignatureException: | |
self.set_status('403') | |
return self.write('hi') | |
msg = parse_message(self.request.body) | |
print msg | |
if msg.type == 'text': | |
reply = create_reply(msg.content, msg) | |
elif msg.type == 'event': | |
if msg.event == 'scan': | |
reply = create_reply('扫描', msg) | |
for c in clients: | |
if c['qid'] == int(msg.scene_id): | |
for user in users: | |
if user['openid'] == msg.source: | |
c['client'].write_message(dict(type='success', data=user['username'])) | |
break | |
else: | |
c['client'].write_message(dict(type='success_and_not_bind', data=msg.source)) | |
elif msg.event == 'subscribe_scan': | |
reply = create_reply('扫描关注', msg) | |
for c in clients: | |
if c['qid'] == int(msg.scene_id): | |
for user in users: | |
if user['openid'] == msg.source: | |
c['client'].write_message(dict(type='success', data=user['username'])) | |
break | |
else: | |
c['client'].write_message(dict(type='success_and_not_bind', data=msg.source)) | |
else: | |
reply = create_reply('Sorry, can not handle this for now', msg) | |
self.write(reply.render()) | |
application = tornado.web.Application([ | |
(r'/', IndexHandler), | |
(r'/user', UserCenterHandler), | |
(r'/user/bind', UserBindHandler), | |
(r'/user/logout', LogoutHandler), | |
(r'/ws', WebSocketHandler), | |
(r'/wechat', WeChatHandler), | |
], | |
template_path='templates', | |
login_url="/", | |
debug=True, | |
cookie_secret='key', | |
) | |
if __name__ == '__main__': | |
application.listen(5000) | |
tornado.ioloop.IOLoop.instance().start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends 'layout.html' %} | |
{% block content %} | |
<div class="container"> | |
<div class="row"> | |
<div class="col-md-3"></div> | |
<div class="col-md-6"> | |
<div class="well"> | |
<form class="form-horizontal" method="POST"> | |
<div class="form-group"> | |
<label for="inputEmail3" class="col-sm-2 control-label">Username</label> | |
<div class="col-sm-10"> | |
<input type="text" class="form-control" id="inputEmail3" placeholder="Username" name="username"> | |
</div> | |
</div> | |
<div class="form-group"> | |
<div class="col-sm-offset-2 col-sm-10"> | |
<button type="submit" class="btn btn-default">Sign in</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
</div> | |
<div class="col-md-3"> | |
<img class="img-thumbnail" src="" id="wechat-qr-img" alt=""> | |
</div> | |
</div> | |
<div class="row" id="wechat-bind" style="display:none;"> | |
<div class="col-md-3"></div> | |
<div class="col-md-6"> | |
<div class="well"> | |
<form class="form-horizontal"> | |
<div class="form-group"> | |
<label for="inputEmail3" class="col-sm-2 control-label">Username</label> | |
<div class="col-sm-10"> | |
<input type="text" class="form-control" placeholder="Username" name="username" id="bind-username"> | |
<input type="hidden" class="form-control" placeholder="" name="" id="bind-openid"> | |
</div> | |
</div> | |
<div class="form-group"> | |
<div class="col-sm-offset-2 col-sm-10"> | |
<button type="button" class="btn btn-default" id="wechat-bind-btn">Bind</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
</div> | |
<div class="col-md-3"></div> | |
</div> | |
</div> | |
{% end %} | |
{% block script %} | |
<script> | |
var ws = new WebSocket('ws://' + window.location.host + '/ws'); | |
$(function() { | |
ws.onopen = function() { | |
console.info('open'); | |
var get_qr = { | |
type: 'get_qr' | |
} | |
ws.send(JSON.stringify(get_qr)); | |
}; | |
var t; | |
ws.onmessage = function (evt) { | |
var msg = JSON.parse(evt.data); | |
console.log(msg); | |
if (msg.type == 'qr') { | |
$('#wechat-qr-img').attr('src', 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' + msg.data.ticket) | |
var expires = msg.data.expires; | |
function run() { | |
console.info(expires); | |
expires--; | |
if (expires == 0) { | |
clearTimeout(t); | |
$('#wechat-qr-img').attr('src', ''); | |
alert('二维码过期'); | |
window.location.reload(); | |
} | |
t = setTimeout(function(){run()}, 1000); | |
} | |
run(); | |
} else if (msg.type == 'success_and_not_bind') { | |
clearTimeout(t); | |
$('#wechat-bind').slideDown(); | |
$('#bind-openid').val(msg.data); | |
} else if (msg.type == 'success') { | |
$.cookie('user', msg.data); | |
window.location.href = '/user'; | |
} | |
}; | |
$('#wechat-bind-btn').click(function() { | |
var username = $('#bind-username').val(); | |
var openid = $('#bind-openid').val(); | |
console.info(username); | |
console.info(openid); | |
$.post('/user/bind', {openid: openid, username: username}) | |
.success(function(data) { | |
console.log(data); | |
if (data.status) { | |
window.location.href = '/user'; | |
} | |
}) | |
.fail(function(data) { | |
console.error(data.responseJSON); | |
}); | |
}); | |
}); | |
</script> | |
{% end %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="zh-CN"> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> | |
<title>Bootstrap 101 Template</title> | |
<!-- Bootstrap --> | |
<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css"> | |
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> | |
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> | |
<!--[if lt IE 9]> | |
<script src="http://cdn.bootcss.com/html5shiv/3.7.2/html5shiv.min.js"></script> | |
<script src="http://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> | |
<![endif]--> | |
</head> | |
<body> | |
{% block content %}{% end %} | |
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 --> | |
<script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script> | |
<!-- 最新的 Bootstrap 核心 JavaScript 文件 --> | |
<script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js"></script> | |
<script src="http://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script> | |
{% block script %} | |
{% end %} | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends 'layout.html' %} | |
{% block content %} | |
<div class="container"> | |
<div class="row"> | |
<div class="col-md-3"></div> | |
<div class="col-md-6"> | |
<div class="well"> | |
当前用户:{{ user }} | |
<a class="btn btn-danger" href="/user/logout">Sign Out</a> | |
</div> | |
</div> | |
<div class="col-md-3"></div> | |
</div> | |
</div> | |
{% end %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment