|
@@ -1,9 +1,14 @@
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
+import time
|
|
|
import json
|
|
|
import requests
|
|
|
+import redis
|
|
|
+import string
|
|
|
+import random
|
|
|
+import hashlib
|
|
|
|
|
|
-__all__ = ("wx_login")
|
|
|
+__all__ = ("wx_login", "WeixinMP", )
|
|
|
|
|
|
try:
|
|
|
unicode = unicode
|
|
@@ -65,12 +70,123 @@ class Map(dict):
|
|
|
super(Map, self).__delitem__(key)
|
|
|
del self.__dict__[key]
|
|
|
|
|
|
+
|
|
|
class WeixinLoginError(WeixinError):
|
|
|
|
|
|
def __init__(self, msg):
|
|
|
super(WeixinLoginError, self).__init__(msg)
|
|
|
|
|
|
|
|
|
+class WeixinMP(object):
|
|
|
+ api_uri = "https://api.weixin.qq.com/cgi-bin"
|
|
|
+
|
|
|
+ def __init__(self, app_id=None, app_secret=None, redis_url=None):
|
|
|
+ self.app_id = app_id
|
|
|
+ self.app_secret = app_secret
|
|
|
+ self.session = requests.Session()
|
|
|
+
|
|
|
+ if redis_url is not None:
|
|
|
+ self.redis_store = redis.StrictRedis.from_url(redis_url)
|
|
|
+ else:
|
|
|
+ self.redis_store = None
|
|
|
+
|
|
|
+ def init_app(self, app):
|
|
|
+ """
|
|
|
+ flask config
|
|
|
+ """
|
|
|
+ app.config.setdefault("WXMP_REDIS_URL", None)
|
|
|
+ app.config.setdefault("WXMP_APP_ID", None)
|
|
|
+ app.config.setdefault("WXMP_APP_SECRET", None)
|
|
|
+
|
|
|
+ self.app_id = app.config.get("WXMP_APP_ID")
|
|
|
+ self.app_secret = app.config.get("WXMP_APP_SECRET")
|
|
|
+ redis_url = app.config.get("WXMP_REDIS_URL")
|
|
|
+
|
|
|
+ assert self.app_id is not None, "APP_ID IS NULL"
|
|
|
+ assert self.app_secret is not None, "APP_SECRET IS NULL"
|
|
|
+ # assert redis_url is not None, "WXMP_REDIS_URL IS NULL"
|
|
|
+ if redis_url:
|
|
|
+ self.redis_store = redis.StrictRedis.from_url(redis_url)
|
|
|
+
|
|
|
+ def fetch(self, method, url, params=None, data=None, headers=None):
|
|
|
+ req = requests.Request(method, url, params=params,
|
|
|
+ data=data, headers=headers)
|
|
|
+ prepped = req.prepare()
|
|
|
+ resp = self.session.send(prepped, timeout=20)
|
|
|
+ data = Map(resp.json())
|
|
|
+ if data.errcode:
|
|
|
+ msg = "%(errcode)d %(errmsg)s" % data
|
|
|
+ raise WeixinError(msg)
|
|
|
+ return data
|
|
|
+
|
|
|
+ def get(self, path, params=None, token=True):
|
|
|
+ url = "{0}{1}".format(self.api_uri, path)
|
|
|
+ params = {} if not params else params
|
|
|
+ token and params.setdefault("access_token", self.access_token)
|
|
|
+ return self.fetch("GET", url, params)
|
|
|
+
|
|
|
+ def gen_token(self):
|
|
|
+ params = dict()
|
|
|
+ params.setdefault("grant_type", "client_credential")
|
|
|
+ params.setdefault("appid", self.app_id)
|
|
|
+ params.setdefault("secret", self.app_secret)
|
|
|
+ data = self.get("/token", params, False)
|
|
|
+ print("-" * 20, data, params)
|
|
|
+ return data.access_token
|
|
|
+
|
|
|
+ @property
|
|
|
+ def access_token(self):
|
|
|
+ """
|
|
|
+ 获取服务端凭证
|
|
|
+ """
|
|
|
+ if self.redis_store:
|
|
|
+ ac_key = "access_token:%s" % self.app_id
|
|
|
+
|
|
|
+ access_token = self.redis_store.get(ac_key)
|
|
|
+ if not access_token:
|
|
|
+ access_token = self.gen_token()
|
|
|
+ self.redis_store.setex(ac_key, 2 * 60 * 60, access_token)
|
|
|
+ return access_token
|
|
|
+ return access_token
|
|
|
+ else:
|
|
|
+ return self.gen_token()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def jsapi_ticket(self):
|
|
|
+ """
|
|
|
+ 获取jsapi ticket
|
|
|
+ """
|
|
|
+ ticket_key = "jsapi_ticket:%s" % self.app_id
|
|
|
+ ticket = self.redis_store.get(ticket_key)
|
|
|
+ if not ticket:
|
|
|
+ params = dict()
|
|
|
+ params.setdefault("type", "jsapi")
|
|
|
+ data = self.get("/ticket/getticket", params, True)
|
|
|
+ self.redis_store.setex(ticket_key, 2 * 60 * 60, data.ticket)
|
|
|
+ return data.ticket
|
|
|
+ return ticket
|
|
|
+
|
|
|
+ @property
|
|
|
+ def nonce_str(self):
|
|
|
+ char = string.ascii_letters + string.digits
|
|
|
+ return "".join(random.choice(char) for _ in range(32))
|
|
|
+
|
|
|
+ def jsapi_sign(self, **kwargs):
|
|
|
+ """
|
|
|
+ 生成签名给js使用
|
|
|
+ """
|
|
|
+ timestamp = str(int(time.time()))
|
|
|
+ nonce_str = self.nonce_str
|
|
|
+ kwargs.setdefault("jsapi_ticket", self.jsapi_ticket)
|
|
|
+ kwargs.setdefault("timestamp", timestamp)
|
|
|
+ kwargs.setdefault("noncestr", nonce_str)
|
|
|
+ raw = [(k, kwargs[k]) for k in sorted(kwargs.keys())]
|
|
|
+ s = "&".join("=".join(kv) for kv in raw if kv[1])
|
|
|
+ print(s)
|
|
|
+ sign = hashlib.sha1(s.encode("utf-8")).hexdigest().lower()
|
|
|
+ return Map(sign=sign, timestamp=timestamp, noncestr=nonce_str)
|
|
|
+
|
|
|
+
|
|
|
class WeixinLogin(object):
|
|
|
|
|
|
def __init__(self, app=None, prefix=""):
|
|
@@ -184,4 +300,5 @@ class WeixinLogin(object):
|
|
|
"""
|
|
|
return self.userinfo(access_token, openid)
|
|
|
|
|
|
+
|
|
|
wx_login = WeixinLogin()
|