1 # coding:utf-8
2
3 import tornado.web
4 import tornado.options
5 import tornado.httpserver
6 import tornado.ioloop
7 import hashlib
8 import xmltodict
9 import time
10 import tornado.gen
11 import json
12 import os
13
14 from tornado.web import RequestHandler
15 from tornado.options import options, define
16 from tornado.httpclient import AsyncHTTPClient, HTTPRequest
17
18
19 WECHAT_TOKEN = "itcast"
20 WECHAT_APP_ID = "wx36766f74dbfeef15"
21 WECHAT_APP_SECRET = "aaf6dbca95a012895eb570f0ba549ee5"
22
23 define("port", default=8000, type=int, help="")
24
25 class AccessToken(object):
26 """access_token辅助类"""
27 _access_token = None
28 _create_time = 0
29 _expires_in = 0
30
31 @classmethod
32 @tornado.gen.coroutine
33 def update_access_token(cls):
34 client = AsyncHTTPClient()
35 url = "https://api.weixin.qq.com/cgi-bin/token?"
36 "grant_type=client_credential&appid=%s&secret=%s" % (WECHAT_APP_ID, WECHAT_APP_SECRET)
37 resp = yield client.fetch(url)
38 dict_data = json.loads(resp.body)
39 if "errcode" in dict_data:
40 raise Exception("wechat server error")
41 else:
42 cls._access_token = dict_data["access_token"]
43 cls._expires_in = dict_data["expires_in"]
44 cls._create_time = time.time()
45
46
47 @classmethod
48 @tornado.gen.coroutine
49 def get_access_token(cls):
50 if time.time() - cls._create_time > (cls._expires_in - 200):
51 # 向微信服务器请求access_token
52 yield cls.update_access_token()
53 raise tornado.gen.Return(cls._access_token)
54 else:
55 raise tornado.gen.Return(cls._access_token)
56
57
58
59 class WechatHandler(RequestHandler):
60 """对接微信服务器"""
61 def prepare(self):
62 signature = self.get_argument("signature")
63 timestamp = self.get_argument("timestamp")
64 nonce = self.get_argument("nonce")
65 tmp = [WECHAT_TOKEN, timestamp, nonce]
66 tmp.sort()
67 tmp = "".join(tmp)
68 real_signature = hashlib.sha1(tmp).hexdigest()
69 if signature != real_signature:
70 self.send_error(403)
71
72 def get(self):
73 echostr = self.get_argument("echostr")
74 self.write(echostr)
75
76 def post(self):
77 xml_data = self.request.body
78 dict_data = xmltodict.parse(xml_data)
79 msg_type = dict_data["xml"]["MsgType"]
80 if msg_type == "text":
81 content = dict_data["xml"]["Content"]
82 """
83 <xml>
84 <ToUserName><![CDATA[toUser]]></ToUserName>
85 <FromUserName><![CDATA[fromUser]]></FromUserName>
86 <CreateTime>12345678</CreateTime>
87 <MsgType><![CDATA[text]]></MsgType>
88 <Content><![CDATA[你好]]></Content>
89 </xml>
90 """
91 resp_data = {
92 "xml":{
93 "ToUserName": dict_data["xml"]["FromUserName"],
94 "FromUserName": dict_data["xml"]["ToUserName"],
95 "CreateTime": int(time.time()),
96 "MsgType": "text",
97 "Content": content,
98 }
99 }
100 self.write(xmltodict.unparse(resp_data))
101 elif msg_type == "event":
102 if dict_data["xml"]["Event"] == "subscribe":
103 """用户关注的事件"""
104 resp_data = {
105 "xml": {
106 "ToUserName": dict_data["xml"]["FromUserName"],
107 "FromUserName": dict_data["xml"]["ToUserName"],
108 "CreateTime": int(time.time()),
109 "MsgType": "text",
110 "Content": u"您来啦,笑而不语",
111 }
112 }
113 if "EventKey" in dict_data["xml"]:
114 event_key = dict_data["xml"]["EventKey"]
115 scene_id = event_key[8:]
116 resp_data["xml"]["Content"] = u"您来啦,笑而不语%s次" % scene_id
117 self.write(xmltodict.unparse(resp_data))
118 elif dict_data["xml"]["Event"] == "SCAN":
119 scene_id = dict_data["xml"]["EventKey"]
120 resp_data = {
121 "xml": {
122 "ToUserName": dict_data["xml"]["FromUserName"],
123 "FromUserName": dict_data["xml"]["ToUserName"],
124 "CreateTime": int(time.time()),
125 "MsgType": "text",
126 "Content": u"您扫描的是%s" % scene_id,
127 }
128 }
129 self.write(xmltodict.unparse(resp_data))
130
131 else:
132 resp_data = {
133 "xml": {
134 "ToUserName": dict_data["xml"]["FromUserName"],
135 "FromUserName": dict_data["xml"]["ToUserName"],
136 "CreateTime": int(time.time()),
137 "MsgType": "text",
138 "Content": "I love itcast",
139 }
140 }
141 self.write(xmltodict.unparse(resp_data))
142
143
144 class QrcodeHandler(RequestHandler):
145 """请求微信服务器生成带参数二维码返回给客户"""
146 @tornado.gen.coroutine
147 def get(self):
148 scene_id = self.get_argument("sid")
149 try:
150 access_token = yield AccessToken.get_access_token()
151 except Exception as e:
152 self.write("errmsg: %s" % e)
153 else:
154 client = AsyncHTTPClient()
155 url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s" % access_token
156 req_data = {"action_name": "QR_LIMIT_SCENE", "action_info": {"scene": {"scene_id": scene_id}}}
157 req = HTTPRequest(
158 url=url,
159 method="POST",
160 body=json.dumps(req_data)
161 )
162 resp = yield client.fetch(req)
163 dict_data = json.loads(resp.body)
164 if "errcode" in dict_data:
165 self.write("errmsg: get qrcode failed")
166 else:
167 ticket = dict_data["ticket"]
168 qrcode_url = dict_data["url"]
169 self.write('<img src="https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s"><br/>' % ticket)
170 self.write('<p>%s</p>' % qrcode_url)
171
172
173 class ProfileHandler(RequestHandler):
174 @tornado.gen.coroutine
175 def get(self):
176 code = self.get_argument("code")
177 client = AsyncHTTPClient()
178 url = "https://api.weixin.qq.com/sns/oauth2/access_token?"
179 "appid=%s&secret=%s&code=%s&grant_type=authorization_code" % (WECHAT_APP_ID, WECHAT_APP_SECRET, code)
180 resp = yield client.fetch(url)
181 dict_data = json.loads(resp.body)
182 if "errcode" in dict_data:
183 self.write("error occur")
184 else:
185 access_toke = dict_data["access_token"]
186 open_id = dict_data["openid"]
187 url = "https://api.weixin.qq.com/sns/userinfo?"
188 "access_token=%s&openid=%s&lang=zh_CN" % (access_toke, open_id)
189 resp = yield client.fetch(url)
190 user_data = json.loads(resp.body)
191 if "errcode" in user_data:
192 self.write("error occur again")
193 else:
194 self.render("index.html", user=user_data)
195
196 """
197 用户最终访问的URL
198 https://open.weixin.qq.com/connect/oauth2/authorize?
199 appid=wx36766f74dbfeef15&redirect_uri=http%3A//www.idehai.com/wechat8000/profile&response_type=code&scope=snsapi_userinfo
200 &state=1#wechat_redirect
201 """
202
203
204 class MenuHandler(RequestHandler):
205 @tornado.gen.coroutine
206 def get(self):
207 try:
208 access_token = yield AccessToken.get_access_token()
209 except Exception as e:
210 self.write("errmsg: %s" % e)
211 else:
212 client = AsyncHTTPClient()
213 url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s" % access_token
214 menu = {
215 "button": [
216 {
217 "type": "view",
218 "name": "我的主页",
219 "url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx36766f74dbfeef15&redirect_uri=http%3A//www.idehai.com/wechat8000/profile&response_type=code&scope=snsapi_userinfo&state=1&connect_redirect=1#wechat_redirect"
220 }
221 ]
222 }
223 req = HTTPRequest(
224 url=url,
225 method="POST",
226 body=json.dumps(menu, ensure_ascii=False)
227 )
228 resp = yield client.fetch(req)
229 dict_data = json.loads(resp.body)
230 if dict_data["errcode"] == 0:
231 self.write("OK")
232 else:
233 self.write("failed")
234
235
236 def main():
237 tornado.options.parse_command_line()
238 app = tornado.web.Application(
239 [
240 (r"/wechat8000", WechatHandler),
241 (r"/qrcode", QrcodeHandler),
242 (r"/wechat8000/profile", ProfileHandler),
243 (r"/menu", MenuHandler),
244 ],
245 template_path=os.path.join(os.path.dirname(__file__), "template")
246 )
247 http_server = tornado.httpserver.HTTPServer(app)
248 http_server.listen(options.port)
249 tornado.ioloop.IOLoop.current().start()
250
251 if __name__ == "__main__":
252 main()