Пример #1
0
class WxHandler(tornado.web.RequestHandler):
    """
    微信handler处理类
    """

    '''微信配置文件'''
    wx_config = WxConfig()
    '''微信网页授权server'''
    wx_author_server = WxAuthorServer()
    '''redis服务'''
    wx_token_cache = TokenCache()

    def post(self, flag):

        if flag == 'wxauthor':
            '''微信网页授权'''
            code = self.get_argument('code')
            state = self.get_argument('state')
            # 获取重定向的url
            redirect_url = self.wx_config.wx_menu_state_map[state]
            logger.debug('【微信网页授权】将要重定向的地址为:redirct_url[' + redirect_url + ']')
            logger.debug('【微信网页授权】用户同意授权,获取code>>>>code[' + code + ']state[' + state + ']')
            if code:
                # 通过code换取网页授权access_token
                data = self.wx_author_server.get_auth_access_token(code)
                openid = data['openid']
                logger.debug('【微信网页授权】openid>>>>openid[' + openid + ']')
                if openid:
                    # 跳到自己的业务界面
                    self.redirect(redirect_url)
                else:
                    # 获取不到openid
                    logger.debug('获取不到openid')
Пример #2
0
class WxKF(object):
    _token_cache = TokenCache()
    _wx_author_server = WxAuthorServer()

    def add_kf(self):
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)
        if access_token:

            url = WxConfig.add_kf + access_token
            data = self.add_kf_data()
            r = requests.post(url, data.encode('utf-8'))
            logger.debug('【微信多客服系统】Response' + str(r.status_code))
            if r.status_code == 200:
                res = r.text
                logger.debug('[微信多客服系统]' + res)
                json_res = json.loads(res)
                if 'errcode' in json.loads(res):
                    errcode = json_res['errcode']
                    return errcode
        else:
            logger.error('微信多客服系统获取不到access_token')

    def add_kf_data(self):
        '''创建客服数据'''
        kf_data = {
            'kf_account': 'bzw_lolface@gh_cacb86407b98',
            'nickname': '小包'
        }
        KF_DATA = json.dumps(kf_data, ensure_ascii=False)
        logger.debug('【微信客服创建客服数据KF_DATA[' + str(KF_DATA) + ']')
        return KF_DATA

    def invite_kf(self):
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)

        if access_token:

            url = WxConfig.invite_kf + access_token
            invite_data = {
                'kf_account': 'bzw_lolface@gh_cacb86407b98',
                'invite_wx': 'bzw_lolface'
            }
            r = requests.post(
                url,
                json.dumps(invite_data, ensure_ascii=False).encode('utf-8'))
            logger.debug('[微信多客服系统]Response' + str(r.status_code))
            if r.status_code == 200:
                res = r.text
                logger.debug('[微信多客服系统]' + res)
                json_res = json.loads(res)
                if 'errcode' in json.loads(res):
                    errcode = json_res('errcode')
                    return errcode
        else:
            logger.error('[微信多客服系统获取不到access_token]')
Пример #3
0
class WdAPI(self):
    
    baseURL = 'https://api.vdian.com/api'
    _token_cache = TokenCache()
    
    def fetch

    def getAccessToken(self):
        return _token_cache.get_cache(self._token_cache.KEY_WD_ACCESS_TOKEN)
Пример #4
0
class WxShedule(object):
    """
    定时任务调度器

    excute                      执行定时器任务
    get_access_token            获取微信全局唯一票据access_token
    get_jsapi_ticket           获取JS_SDK权限签名的jsapi_ticket
    """
    _token_cache = TokenCache()  # 微信token缓存实例
    _expire_time_access_token = 7000 * 1000  # token过期时间
    _check_menu_period = 24 * 60 * 60

    def excute(self):
        """执行定时器任务"""
        logger.info('【获取微信全局唯一票据access_token】>>>执行定时器任务')
        tornado.ioloop.IOLoop.instance().call_later(0, self.get_access_token)
        tornado.ioloop.PeriodicCallback(
            self.get_access_token, self._expire_time_access_token).start()

    def get_access_token(self):
        """获取微信全局唯一票据access_token"""
        url = WxConfig.config_get_access_token_url
        r = requests.get(url)
        logger.info('【获取微信全局唯一票据access_token】Response[' + str(r.status_code) +
                    ']')
        if r.status_code == 200:
            res = r.text
            logger.info('【获取微信全局唯一票据access_token】>>>' + res)
            d = json.loads(res)
            if 'access_token' in d.keys():
                access_token = d['access_token']
                # 添加至redis中
                self._token_cache.set_access_cache(
                    self._token_cache.KEY_ACCESS_TOKEN, access_token)
                # 获取JS_SDK权限签名的jsapi_ticket
                self.get_jsapi_ticket()
                return access_token
            elif 'errcode' in d.keys():
                errcode = d['errcode']
                logger.info(
                    '【获取微信全局唯一票据access_token-SDK】errcode[' + errcode +
                    '] , will retry get_access_token() method after 10s')
                tornado.ioloop.IOLoop.instance().call_later(
                    10, self.get_access_token)
        else:
            logger.error('【获取微信全局唯一票据access_token】request access_token error' +
                         ',will retry get_access_token() method after 10s')
            tornado.ioloop.IOLoop.instance().call_later(
                10, self.get_access_token)

    def get_jsapi_ticket(self):
        """获取JS_SDK权限签名的jsapi_ticket"""
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)
        if access_token:
            url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi' % access_token
            r = requests.get(url)
            logger.info('【微信JS-SDK】获取JS_SDK权限签名的jsapi_ticket的Response[' +
                        str(r.status_code) + ']')
            if r.status_code == 200:
                res = r.text
                logger.info('【微信JS-SDK】获取JS_SDK权限签名的jsapi_ticket>>>>' + res)
                d = json.loads(res)
                errcode = d['errcode']
                if errcode == 0:
                    jsapi_ticket = d['ticket']
                    # 添加至redis中
                    self._token_cache.set_access_cache(
                        self._token_cache.KEY_JSAPI_TICKET, jsapi_ticket)
                else:
                    logger.info(
                        '【微信JS-SDK】获取JS_SDK权限签名的jsapi_ticket>>>>errcode[' +
                        errcode + ']')
                    logger.info(
                        '【微信JS-SDK】request jsapi_ticket error, will retry get_jsapi_ticket() method after 10s'
                    )
                    tornado.ioloop.IOLoop.instance().call_later(
                        10, self.get_jsapi_ticket)
            else:
                logger.info(
                    '【微信JS-SDK】request jsapi_ticket error, will retry get_jsapi_ticket() method after 10s'
                )
                tornado.ioloop.IOLoop.instance().call_later(
                    10, self.get_jsapi_ticket)
        else:
            logger.error(
                '【微信JS-SDK】获取JS_SDK权限签名的jsapi_ticket时,access_token获取失败, will retry get_access_token() method after 10s'
            )
            tornado.ioloop.IOLoop.instance().call_later(
                10, self.get_access_token)

    def get_menu(self):
        """获取menu,如果没有menu则进行创建"""
        menu = WxMenuServer()

        try:

            data = menu.get_menu()
            logger.debug('微信获取menu' + str(data))
            if data == 0:
                pass
            else:
                menu.create_menu()
        except Exception as e:

            logger.error('Exception:' + e)
Пример #5
0
class WxSignatureHandler(tornado.web.RequestHandler):
    """
    微信服务器签名验证, 消息回复

    check_signature: 校验signature是否正确
    """

    pattern = re.compile(r'\d{15}')
    _token_cache = TokenCache()
    _media_cache = WxMediaCache()
    workpath = os.getcwd()

    def data_received(self, chunk):
        pass

    def get(self):
        try:
            signature = self.get_argument('signature')
            timestamp = self.get_argument('timestamp')
            nonce = self.get_argument('nonce')
            echostr = self.get_argument('echostr')
            result = self.check_signature(signature, timestamp, nonce)
            logger.debug('微信sign校验,signature=' + signature + ',&timestamp=' +
                         timestamp + '&nonce=' + nonce + '&echostr=' + echostr)
            if result:
                logger.debug('微信sign校验,返回echostr=' + echostr)
                self.write(echostr)
            else:
                logger.error('微信sign校验,---校验失败')
        except Exception as e:
            logger.error('微信sign校验,---Exception' + str(e))

    @tornado.web.asynchronous
    def post(self):
        body = self.request.body
        logger.debug('微信消息回复中心】收到用户消息' + str(body.decode('utf-8')))
        data = ET.fromstring(body)
        ToUserName = data.find('ToUserName').text
        FromUserName = data.find('FromUserName').text
        self._from_name = FromUserName
        self._to_name = ToUserName
        MsgType = data.find('MsgType').text
        if MsgType == 'text' or MsgType == 'voice':
            '''文本消息 or 语音消息'''
            try:
                MsgId = data.find("MsgId").text
                if MsgType == 'text':
                    Content = data.find('Content').text  # 文本消息内容
                    results = self.pattern.search(Content)
                    if results:
                        # 找到了符合订单ID的内容
                        order_id = results.group()
                        self._order_id = order_id
                        http_client = tornado.httpclient.AsyncHTTPClient()
                        token = self._token_cache.get_cache(
                            self._token_cache.KEY_WD_ACCESS_TOKEN)
                        params = {"order_id": order_id}
                        public = {
                            "access_token": token,
                            "version": "1.0",
                            "format": "json",
                            "method": "vdian.order.get"
                        }
                        url = r'https://api.vdian.com/api?param={"order_id":"%s"}&public={"method":"vdian.order.get","access_token":"%s","version":"1.0","format":"json"}' % (
                            order_id, token)
                        http_client.fetch(url, callback=self.on_response)
                    else:
                        reply_content = WxConfig.COMMON_COPYWRITE
                        CreateTime = int(time.time())
                        out = self.reply_text(FromUserName, ToUserName,
                                              CreateTime, reply_content)
                        self.write(out)
                        self.finish()
            except Exception as e:
                logger.error(str(e))
                self.finish()

        elif MsgType == 'event':
            '''接收事件推送'''
            try:
                CreateTime = int(time.time())
                Event = data.find('Event').text
                if Event == 'subscribe':
                    # 关注事件
                    out = self.reply_text(FromUserName, ToUserName, CreateTime,
                                          WxConfig.ATTENTION_INIT_COPYWRITE_1)
                    self.write(out)
                    self.send_service_message_text(
                        WxConfig.ATTENTION_INIT_COPYWRITE_2)
                elif Event == 'CLICK':
                    key = data.find('EventKey').text
                    if key == 'reprint':
                        # 转载
                        out = self.reply_text(FromUserName, ToUserName,
                                              CreateTime,
                                              WxConfig.REPRINT_COPYWRITE)
                        self.write(out)
            except Exception as e:
                logger.error(str(e))
            finally:
                self.finish()
        elif MsgType == 'image':
            try:
                CreateTime = int(time.time())
                out = self.reply_text(FromUserName, ToUserName, CreateTime,
                                      WxConfig.PART_IN_GUESSGANME_WAITTING)
                self.write(out)
            except Exception as e:
                logger.error(str(e))
            finally:
                self.finish()

    def reply_text(self, FromUserName, ToUserName, CreateTime, Content):
        textTpl = """<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <Content><![CDATA[%s]]></Content></xml>"""
        out = textTpl % (FromUserName, ToUserName, CreateTime, "text", Content)
        return out

    def reply_image(self, FromUserName, ToUserName, CreateTime, Media_ID):
        textTpl = """<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[%s]]></MediaId></Image></xml>"""
        out = textTpl % (FromUserName, ToUserName, CreateTime, Media_ID)
        return out

    def check_signature(self, signature, timestamp, nonce):
        """校验token是否正µ确"""
        token = WxConfig.AppCustomToken
        L = [timestamp, nonce, token]
        L.sort()
        s = L[0] + L[1] + L[2]
        sha1 = hashlib.sha1(s.encode('utf-8')).hexdigest()
        logger.debug('sha1=' + sha1 + '&signature=' + signature)
        return sha1 == signature

    def get_font_path(self):
        """获取字体的路径"""
        return self.workpath + "/core/product/SourceHanSerifCN-Medium.otf"

    def get_random_path(self):
        """获取随机源图片本地路径"""
        randomNumber = random.randint(1, 100)
        if randomNumber <= 35:
            peakNumber = 0
        elif randomNumber <= 70:
            peakNumber = 1
        elif randomNumber <= 85:
            peakNumber = 2
        else:
            peakNumber = 3
        return self.workpath + "/core/static/Puzzle_%d.jpeg" % peakNumber

    def send_service_message_text(self, message):
        """发送文字类型客服消息"""
        token = self._token_cache.get_cache(self._token_cache.KEY_ACCESS_TOKEN)
        url = WxConfig.server_message_send_url + token
        headers = {'content-type': 'charset=utf8'}
        dic = {
            "touser": self._from_name,
            "msgtype": "text",
            "text": {
                "content": message
            }
        }
        return requests.post(url=url,
                             data=json.dumps(
                                 dic, ensure_ascii=False).encode('utf8'),
                             headers=headers)

    def on_response(self, response):
        try:
            CreateTime = int(time.time())
            if response.error:
                out = self.reply_text(self._fddrom_name, self._to_name,
                                      CreateTime,
                                      WxConfig.HTTP_RESPONSE_ERROR_COPYWRITE)
                self.write(out)
            else:
                CreateTime = int(time.time())
                res_json = json.loads(response.body)
                if res_json["status"]["status_code"] != 0 or res_json[
                        "result"]["status_ori"] == 10:
                    out = self.reply_text(self._from_name, self._to_name,
                                          CreateTime,
                                          WxConfig.PART_IN_FAILURE_COPYWRITE)
                    self.write(out)
                    logger.info("==========非法订单以及请求==========")
                    logger.info(res_json)
                    return
                self.send_service_message_text(
                    WxConfig.PART_IN_SUCCESS_COPYWRITE)
                name = res_json["result"]["buyer_info"]["name"]
                exit_media_id = self._media_cache.get_cache(self._order_id)
                if exit_media_id is not None:
                    out = self.reply_image(self._from_name, self._to_name,
                                           CreateTime, exit_media_id)
                    self.write(out)
                else:
                    token = self._token_cache.get_cache(
                        self._token_cache.KEY_ACCESS_TOKEN)
                    rawImagePath = self.get_random_path()
                    playload_image = {'access_token': token, 'type': 'image'}
                    logger.info("【新创建图片】" + rawImagePath)
                    namefont = ImageFont.truetype(self.get_font_path(), 20)
                    idFont = ImageFont.truetype(self.get_font_path(), 12)
                    im = Image.open(rawImagePath)
                    draw = ImageDraw.Draw(im)
                    draw.text((340, 363),
                              name[0:9],
                              fill=(0, 0, 0),
                              font=namefont)
                    draw.text((490, 980),
                              self._order_id,
                              fill=(165, 165, 165),
                              font=idFont)
                    newPath = self.workpath + "/core/product/" + self._order_id + '.jpeg'
                    im.save(newPath)
                    data = {'media': open(newPath, 'rb')}
                    r = requests.post(
                        url=
                        'http://file.api.weixin.qq.com/cgi-bin/media/upload',
                        params=playload_image,
                        files=data)
                    image_json = json.loads(r.text)
                    media_id = image_json["media_id"]
                    self._media_cache.set_cache(self._order_id, media_id)
                    out = self.reply_image(self._from_name, self._to_name,
                                           CreateTime, media_id)
                    self.write(out)
        except Exception as e:
            logger.error(str(e))
        finally:
            self.finish()
Пример #6
0
class WxMenuServer(object):
    """
    微信自定义菜单

    create_menu                     自定义菜单创建接口
    get_menu                        自定义菜单查询接口
    delete_menu                     自定义菜单删除接口
    create_menu_data                创建菜单数据
    """

    _token_cache = TokenCache()  # 微信token缓存
    _wx_author_server = WxAuthorServer()  # 微信网页授权server

    def create_menu(self):
        """自定义菜单创建接口"""
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)
        if access_token:
            url = WxConfig.menu_create_url + access_token
            data = self.create_menu_data()
            r = requests.post(url, data.encode('utf-8'))
            logger.debug('【微信自定义菜单】自定义菜单创建接口Response[' + str(r.status_code) +
                         ']')
            if r.status_code == 200:
                res = r.text
                logger.debug('【微信自定义菜单】自定义菜单创建接口' + res)
                json_res = json.loads(res)
                if 'errcode' in json_res.keys():
                    errcode = json_res['errcode']
                    return errcode
        else:
            logger.error('【微信自定义菜单】自定义菜单创建接口获取不到access_token')

    def get_menu(self):
        """自定义菜单查询接口"""
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)
        if access_token:
            url = WxConfig.menu_get_url + access_token
            r = requests.get(url)
            logger.debug('【微信自定义菜单】自定义菜单查询接口Response[' + str(r.status_code) +
                         ']')
            if r.status_code == 200:
                res = r.text
                logger.debug('【微信自定义菜单】自定义菜单查询接口' + res)
                json_res = json.loads(res)
                if 'errcode' in json_res.keys():
                    errcode = json_res['errcode']
                    if errcode == 0:
                        return json_res
                    return errcode
        else:
            logger.error('【微信自定义菜单】自定义菜单查询接口获取不到access_token')

    def delete_menu(self):
        """自定义菜单删除接口"""
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)
        if access_token:
            url = WxConfig.menu_delete_url + access_token
            r = requests.get(url)
            logger.debug('【微信自定义菜单】自定义菜单删除接口Response[' + str(r.status_code) +
                         ']')
            if r.status_code == 200:
                res = r.text
                logger.debug('【微信自定义菜单】自定义菜单删除接口' + res)
                json_res = json.loads(res)
                if 'errcode' in json_res.keys():
                    errcode = json_res['errcode']
                    return errcode
        else:
            logger.error('【微信自定义菜单】自定义菜单删除接口获取不到access_token')

    def create_menu_data(self):
        """创建菜单数据"""
        menu_data = {'button': []}  # 大菜单
        menu_l1_1 = {'name': '最新优惠', 'sub_button': []}

        menu_l1_2 = {'name': '会员中心', 'sub_button': []}
        menu_l2_1 = {
            'type': 'view',
            'name': '会员享9折优惠',
            'url': self._wx_author_server.get_code_url('menuIndex0')
        }

        menu_l2_2 = {
            'type': 'view',
            'name': '申请会员',
            'url': self._wx_author_server.get_code_url('apply_vip')
        }
        menu_l1_1['sub_button'].append(menu_l2_1)
        menu_l1_2['sub_button'].append(menu_l2_2)
        menu_data['button'].append(menu_l1_1)
        menu_data['button'].append(menu_l1_2)
        MENU_DATA = json.dumps(menu_data, ensure_ascii=False)
        logger.debug('【微信自定义菜单】创建菜单数据MENU_DATA[' + str(MENU_DATA) + ']')
        return MENU_DATA
Пример #7
0
class WxMenuServer(object):
    """
    微信自定义菜单

    create_menu                     自定义菜单创建接口
    get_menu                        自定义菜单查询接口
    delete_menu                     自定义菜单删除接口
    create_menu_data                创建菜单数据
    """

    _token_cache = TokenCache()  # 微信token缓存

    def create_menu(self):
        """自定义菜单创建接口"""
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)
        if access_token:
            url = WxConfig.menu_create_url + access_token
            data = self.create_menu_data()
            r = requests.post(url, data.encode('utf-8'))
            logger.debug('【微信自定义菜单】自定义菜单创建接口Response[' + str(r.status_code) +
                         ']')
            if r.status_code == 200:
                res = r.text
                logger.debug('【微信自定义菜单】自定义菜单创建接口' + res)
                json_res = json.loads(res)
                if 'errcode' in json_res.keys():
                    errcode = json_res['errcode']
                    return errcode
        else:
            logger.error('【微信自定义菜单】自定义菜单创建接口获取不到access_token')

    def get_menu(self):
        """自定义菜单查询接口"""
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)
        if access_token:
            url = WxConfig.menu_get_url + access_token
            r = requests.get(url)
            logger.debug('【微信自定义菜单】自定义菜单查询接口Response[' + str(r.status_code) +
                         ']')
            if r.status_code == 200:
                res = r.text
                logger.debug('【微信自定义菜单】自定义菜单查询接口' + res)
                json_res = json.loads(res)
                if 'errcode' in json_res.keys():
                    errcode = json_res['errcode']
                    return errcode
        else:
            logger.error('【微信自定义菜单】自定义菜单查询接口获取不到access_token')

    def delete_menu(self):
        """自定义菜单删除接口"""
        access_token = self._token_cache.get_cache(
            self._token_cache.KEY_ACCESS_TOKEN)
        if access_token:
            url = WxConfig.menu_delete_url + access_token
            r = requests.get(url)
            logger.debug('【微信自定义菜单】自定义菜单删除接口Response[' + str(r.status_code) +
                         ']')
            if r.status_code == 200:
                res = r.text
                logger.debug('【微信自定义菜单】自定义菜单删除接口' + res)
                json_res = json.loads(res)
                if 'errcode' in json_res.keys():
                    errcode = json_res['errcode']
                    return errcode
        else:
            logger.error('【微信自定义菜单】自定义菜单删除接口获取不到access_token')

    def create_menu_data(self):
        """创建菜单数据"""
        menu_data = {
            "button": [{
                "name":
                "精选内容",
                "sub_button": [{
                    "type":
                    "view",
                    "name":
                    "精选文章",
                    "url":
                    "http://mp.weixin.qq.com/mp/homepage?__biz=MjM5Nzc5MDkwMQ==&hid=3&sn=70ce35abda6a8236f90a3a36e964dc2d"
                }, {
                    "type":
                    "view",
                    "name":
                    "影评剧评",
                    "url":
                    "http://mp.weixin.qq.com/mp/homepage?__biz=MjM5Nzc5MDkwMQ==&hid=4&sn=aeaee3a48324880d0c5e6ea335d5b9cc"
                }, {
                    "type":
                    "view",
                    "name":
                    "明星专访",
                    "url":
                    "http://mp.weixin.qq.com/mp/homepage?__biz=MjM5Nzc5MDkwMQ==&hid=5&sn=24c252b77d42b6d93bd021bbc4eaf67f"
                }]
            }, {
                "type":
                "view",
                "name":
                "李易峰专刊",
                "url":
                "https://weidian.com/s/209793342?ifr=shopdetail&wfr=c"
            }, {
                "name":
                "联系我们",
                "sub_button": [{
                    "type":
                    "media_id",
                    "name":
                    "加入我们",
                    "media_id":
                    "b-Ij3Ifb-J_iT6cv09d5mOV7UvvCeEZ9fQqxIEqAXW0"
                }, {
                    "type": "click",
                    "name": "欢迎转载",
                    "key": "reprint"
                }, {
                    "type":
                    "media_id",
                    "name":
                    "商务合作",
                    "media_id":
                    "b-Ij3Ifb-J_iT6cv09d5mKHcKOLuXzDAqMAT9ns8XWU"
                }]
            }]
        }
        MENU_DATA = json.dumps(menu_data, ensure_ascii=False)
        logger.debug('【微信自定义菜单】创建菜单数据MENU_DATA[' + str(MENU_DATA) + ']')
        return MENU_DATA
Пример #8
0
            'url': url
        }

    def __create_nonce_str(self):
        return ''.join(
            random.choice(string.ascii_letters + string.digits)
            for _ in range(15))

    def __create_timestamp(self):
        return int(time.time())

    def sign(self):
        string = '&'.join([
            '%s=%s' % (key.lower(), self.ret[key]) for key in sorted(self.ret)
        ])
        self.ret['signature'] = hashlib.sha1(
            string.encode('utf-8')).hexdigest()
        logger.debug('【微信JS-SDK】获取JS-SDK权限签名>>>>dict[' + str(self.ret) + ']')
        return self.ret


if __name__ == '__main__':

    token_cache = TokenCache()

    jsapi_ticket = token_cache.get_cache(token_cache.KEY_JSAPI_TICKET)
    # 注意 URL 一定要动态获取,不能 hardcode
    url = '%s/page/index' % WxConfig.AppHost
    sign = WxSign(jsapi_ticket, url)
    print(sign.sign())