关于Centos的Redis 安装:

       上一章我们结束了测试号的申请,也在公众平台页面控制台熟悉了一些重要的数据,接下来就是项目的编写。我个人很随意,用的编写环境是Win10,测试环境是自己租的阿里云服务器,Centos7,基本配置都已搞定,包括Python3 以及所需包,Redis数据库等。下面就简单说一下项目是实现哪些功能:

1、 接口验证,这是自己编写后台必要的环节;

2、回复功能,包括对客户发送的语音,文字的回复,关注事件回复;

3、自定义菜单功能,并对点击菜单的事件进行响应;

4、网页授权,这个是为了跳转自定义页面实现业务逻辑功能,因为我们必须进行验证拿到用户的唯一标识openid;

5、调用微信公众平台的接口实现开放功能,比如扫码,分享等等,当然本次项目我只实现airkiss智能配网,因为我是搞硬件的。

一、下面是整个项目的文件以及简易流程图: 

python 公众号应答 python项目公众号_python 公众号应答

 二、下图是工程目录

python 公众号应答 python项目公众号_tornado_02

 三、文件简单描述

1、wxconfig.py 是我们存放基本配置信息的文件,包括AppId,AppSecret,一些自定义菜单所属url等等。

2、wxmenu.py 是我们自定义菜单的文件,它可以独立运行,仅需要运行一次即可,在菜单变动的时候我们可以选择手动执行,当然也可以放在一些文件中,让它定时执行或者触发执行,在本次工程中我让它在项目运行的时候执行一次。

3、wxcache.py 是基于redis的access_token和jsapi_ticket缓存控制文件,因为微信接口需要的access_token和jsapi_ticket会过期,需要定时刷新,并且获取它们的接口有调用次数限制,因此必须严格控制它们的存在,保证所有模块调用它们不会产生冲突。

4、wxtoken.py 是控制access_token和jsapi_ticket更新文件,就是中枢服务器。

5、wxreply.py 是处理客户行为数据的文件,包括对菜单的响应和留言的回复。

6、wxauthorize.py 是对用户请求自定义页面的拦截,主要是为了拿到用户的openid,所设的关卡,拿到之后进行放行。

7、wxsign.py 是对js-sdk接口调用前的签名,没有这个步骤是没办法进行调用微信开放功能的。

8、wxlogger.py 是日志模块 负责监控程序的运行状态

9、wxhandler.py 是路由指向的类所在模块,包括三个路由 配置和回复路由、页面请求路由和签名请求路由。

10、main.py 是程序的入口文件,对token的定时任务初始化 自定义菜单的创建 端口绑定以及路由声明等等。

 11、html文件时页面文件

12、server.* 是https ssl证书文件,可以不使用签名证书 用http服务器也是可以的

四、上完整代码:

1、main.py

import sys
sys.path.append("./handler")
# 将 handler 目录下的文件放到和main.py同级的目录
import tornado.ioloop
import tornado.web
from tornado import httpserver
import time

from wxtoken import WxShedule       # 维护 token 更新模块
from wxmenu import WxMenuServer     # 自定义菜单模块
from wxhandler import pageHandler,wxStartHandler,getSignHandler # 路由模块

def main():
    application = tornado.web.Application([
        (r'/start', wxStartHandler),    # 验证配置接口和消息回复路由
        (r'/sign',getSignHandler),      # 获取js-sdk签名路由
        (r'/page(.*)', pageHandler),    # 请求微信自定义web页面
        ],
        autoreload=False,
        debug=False
    )
    #application.listen(9000) #http服务器开启
    # 下面代码是配置https服务器,当然选用http服务器也是可以的
    server = httpserver.HTTPServer(application, ssl_options={
           "certfile":"server.crt",
           "keyfile": "server.key",
        }
    )
    # 执行定时任务,定时刷新获取 access_token和jsapi_ticket
    wx_shedule = WxShedule()
    wx_shedule.excute()
    # 给点时间去获取token
    time.sleep(3)
    # 初始化一次菜单
    wx_menu_server = WxMenuServer()
    wx_menu_server.create_menu()
    server.listen(9000)# 单进程开启
    tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
    main()

2、wxtoken.py

import tornado.ioloop
import requests
import json
from wxconfig import WxConfig
from wxcache import TokenCache
from wxlogger import logger

class WxShedule(object):
    """负责access_token和jsapi_ticket的更新"""
    _token_cache = TokenCache()  # 微信token缓存实例
    _expire_time_access_token = 7000 * 1000  # token过期时间

    def excute(self):
        """执行定时器任务"""
        # IOLoop.instance().call_later(delay, callback, *args, **kwargs)
        # 延时delay秒之后,将callback加入到tornado的加入到的处理队列里面,异步调用只调用一次
        tornado.ioloop.IOLoop.instance().call_later(0, self.get_access_token)
        # tornado.ioloop.PeriodicCallback(callback, callback_time, io_loop=None)
        # callback设定定时调用的方法 callback_time设定每次调用之间的间隔,单位毫秒
        tornado.ioloop.PeriodicCallback(self.get_access_token, self._expire_time_access_token).start()

    def get_access_token(self):
        """获取微信全局唯一票据access_token"""
        try:
            url = WxConfig.get_access_token_url
            r = requests.get(url)
            if r.status_code == 200:
                d = json.loads(r.text)
                if 'access_token' in d.keys():
                    access_token = d['access_token']
                    # 添加至redis中
                    self._token_cache.set_access_cache('access_token', access_token)
                    # 获取JS_SDK权限签名的jsapi_ticket
                    self.get_jsapi_ticket()
                else:
                    errcode = d['errcode']
                    # 出现错误10s之后调用一次,获取access_token
                    tornado.ioloop.IOLoop.instance().call_later(10, self.get_access_token)
            else:
                # 网络错误10s之后调用一次,获取access_token
                tornado.ioloop.IOLoop.instance().call_later(10, self.get_access_token)
        except Exception as e:
            logger.error('wxtoken get_access_token'+str(e))
                
    def get_jsapi_ticket(self):
        """获取JS_SDK权限签名的jsapi_ticket"""
        try:
            # 从redis中获取access_token
            access_token = self._token_cache.get_cache('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)
                if r.status_code == 200:
                    d = json.loads(r.text)
                    errcode = d['errcode']
                    if errcode == 0:
                        jsapi_ticket = d['ticket']
                        # 添加至redis中
                        self._token_cache.set_js_cache('jsapi_ticket', jsapi_ticket)
                    else:
                        tornado.ioloop.IOLoop.instance().call_later(10, self.get_jsapi_ticket)
                else:
                    # 网络错误 重新获取
                    tornado.ioloop.IOLoop.instance().call_later(10, self.get_jsapi_ticket)
            else:
                # access_token已经过期 重新获取
                tornado.ioloop.IOLoop.instance().call_later(10, self.get_access_token)
        except Exception as e:
            logger.error('wxtoken get_jsapi_ticket'+str(e))

 3、wxsign.py

import time
import random
import string
import hashlib
from wxcache import TokenCache
from wxlogger import logger

def get_js_sdk_sign(url):
    """获取调用js-sdk必要的数据 nonceStr timestamp signature"""
    try:
        _token_cache = TokenCache()  # 微信token缓存实例
        jsapi_ticket = _token_cache.get_cache('jsapi_ticket') # 从redis中提取jsapi_ticket
        if jsapi_ticket:
            nonceStr=''.join(random.choice(string.ascii_letters + string.digits) for _ in range(15))
            timestamp=int(time.time())
            url=url
            ret = {
                'nonceStr': nonceStr,
                'jsapi_ticket': jsapi_ticket,
                'timestamp': timestamp,
                'url': url
            }
            _string = '&'.join(['%s=%s' % (key.lower(), ret[key]) for key in sorted(ret)])
            ret['signature'] = hashlib.sha1(_string.encode('utf-8')).hexdigest()
            return ret
    except Exception as e:
        logger.error('wxsign get_js_sdk_sign'+str(e))

4、wxreply.py

from wxlogger import logger

"""这是一个处理客户发送信息的文件"""
def reply_text(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 receive_msg(msg):
    # 这是一个将疑问改成成熟句子的函数,例如:你好吗 公众号回复:你好
    if msg[-1] == u'吗':
        return msg[:len(msg)-1]
    elif len(msg)>2 and msg[-2] == u'吗' :
        return msg[:len(msg)-2]
    else:
        return "你说的话我好像不明白?"

def receive_event(event,key):
    # 如果是关注公众号事件
    if event == 'subscribe':
        return "感谢关注!"
    # 如果是点击菜单拉取消息事件
    elif event == 'CLICK':
        # 接下来就是根据你点击不同的菜单拉去不同的消息啦
        # 我为了省事,不进行判断啦,如果需要判断请根据 key进行判断
        return "你点击了菜单"+key
    # 如果是点击菜单跳转Url事件,不做任何处理因为微信客户端会自行处理
    elif event == 'VIEW':
        return None

5、wxmenu.py

from wxconfig import WxConfig
from wxcache import TokenCache
from wxauthorize import WxAuthorServer
from wxlogger import logger
import requests
import json

class WxMenuServer(object):
    """这是一个创建自定义菜单的文件,当你需要更新菜单的时候执行这个文件"""
    token_cache = TokenCache()  # 微信token缓存对象
    # 微信网页授权server,目的是为了重定向,类似关卡
    wx_author_server = WxAuthorServer()  

    def create_menu(self):
        """
        自定义菜单创建接口,这个非常灵活,
        我们可以设置权限,可以传入参数等等,
        我们这边就直接写死了
        """
        try:
            access_token=self.token_cache.get_cache('access_token')
            if not access_token:
                logger.error('创建菜单 获取 token失败')
                return None
            url = WxConfig.menu_create_url + access_token
            data = self.create_menu_data()
            r = requests.post(url, data.encode('utf-8'))
            if not r.status_code == 200:
                logger.error('创建菜单 网络错误')
                return None
            json_res = json.loads(r.text)
            if 'errcode' in json_res.keys():
                errcode = json_res['errcode']
                return errcode
        except Exception as e:
            logger.error('wxmenu create_menu'+str(e))

    def get_menu(self):
        """自定义菜单查询接口"""
        try:
            access_token=self.token_cache.get_cache('access_token')
            if not access_token:
                return None
            url = WxConfig.menu_get_url + access_token
            r = requests.get(url)
            if not r.status_code == 200:
                return None
            json_res = json.loads(r.text)
            if 'errcode' in json_res.keys():
                errcode = json_res['errcode']
                logger.error('自定义菜单查询失败!')
                return errcode
        except Exception as e:
            logger.error('wxmenu get_menu'+str(e))

    def delete_menu(self):
        """自定义菜单删除接口"""
        try:
            access_token=self.token_cache.get_cache('access_token')
            if not access_token:
                return None
            url = WxConfig.menu_delete_url + access_token
            r = requests.get(url)
            if not r.status_code == 200:
                return None
            json_res = json.loads(r.text)
            if 'errcode' in json_res.keys():
                errcode = json_res['errcode']
                logger.error('自定义菜单删除失败')
                return errcode
        except Exception as e:
            logger.error('wxmenu delete_menu'+str(e))

    def create_menu_data(self):
        """创建菜单数据"""
        menu_data = {'button': []}  # 大菜单
        menu_Index0 = {
            'type': 'click',
            'name': '一级菜单',
            "key":  "menu1"
        }
        menu_data['button'].append(menu_Index0)
        menu_Index1 = {
            "name": "二级菜单",
            "sub_button":
            [
                {
                    "type": "view",
                    "name": "test",
                    "url": self.wx_author_server.get_code_url('test')
                },
                {
                    "type": "click",
                    "name": "click",
                    "key":  "click"
                }
            ]
        }
        menu_data['button'].append(menu_Index1)
        # 菜单三 我们让它请求页面,验证js-sdk权限
        menu_Index2 = {
            'type': 'view',
            'name': 'airkiss',
            "url":  self.wx_author_server.get_code_url('airkiss')
        }
        menu_data['button'].append(menu_Index2)
        menu_data = json.dumps(menu_data, ensure_ascii=False)
        return menu_data

if __name__ == '__main__':
    wx_menu_server = WxMenuServer()
    wx_menu_server.create_menu()

6、wxlogger.py

import logging
from logging import Logger

log_path = './out.log'
error_path = './error.log'
'''日志管理类 负责开发过程中的数据的追踪'''
def init_logger(logger_name):
    if logger_name not in Logger.manager.loggerDict:
        # 创建一个logger
        logger = logging.getLogger(logger_name)
        logger.setLevel(logging.INFO)  # 设置最低级别

        # 定义handler的输出格式
        df = '%Y-%m-%d %H:%M:%S'
        format_str = '[%(asctime)s]: %(name)s %(levelname)s %(lineno)s %(message)s'
        formatter = logging.Formatter(format_str, df)

        # 创建一个handler,用于写入日志文件
        fh = logging.FileHandler(log_path,encoding = 'utf-8')  # 指定utf-8格式编码,避免输出的日志文本乱码
        fh.setLevel(logging.INFO)
        fh.setFormatter(formatter)

        # 创建一个handler,用于将日志输出到控制台
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(formatter)

        # 给logger添加handler
        logger.addHandler(fh)
        logger.addHandler(ch)
        
        # 创建一个handler,用于写入错误日志文件
        efh = logging.FileHandler(error_path,encoding = 'utf-8')  # 指定utf-8格式编码,避免输出的日志文本乱码
        efh.setLevel(logging.ERROR)
        efh.setFormatter(formatter)

        # 创建一个handler,用于将错误日志输出到控制台
        ech = logging.StreamHandler()
        ech.setLevel(logging.ERROR)
        ech.setFormatter(formatter)     
        # 给logger添加handler
        logger.addHandler(efh)
        logger.addHandler(ech)

    logger = logging.getLogger(logger_name)
    return logger

logger = init_logger('wx_run_log')

7、wxhandler.py

import tornado.web
# 这是python 标准库 用来处理 xml文件的
import xml.etree.ElementTree as ET
import hashlib
import time
# wxreply文件是我写关于处理回复的函数,我们导出来必要的函数
from wxreply import receive_msg,receive_event,reply_text
from wxconfig import WxConfig
from wxcache import TokenCache
from wxauthorize import WxAuthorServer
from wxlogger import logger
from wxsign import get_js_sdk_sign

class wxStartHandler(tornado.web.RequestHandler):
    """
    微信服务器签名验证和消息回复
    check_signature: 校验signature是否正确
    """
    def check_signature(self, signature, timestamp, nonce):
        """校验token是否正确"""
        # 这个是token 和我们在微信公众平台配置接口填写一致
        token = 'iotbird'
        L = [timestamp, nonce, token]
        L.sort()
        s = L[0] + L[1] + L[2]
        sha1 = hashlib.sha1(s.encode('utf-8')).hexdigest()
        # 对于验证结果返回true or false
        return sha1 == signature
    #
    def get(self):
        """这是get请求,处理配置接口验证的"""
        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)
            if result:
                self.write(echostr)
            else:
                logger.error('微信sign校验,---校验失败')
        except Exception as e:
            logger.error('wxhandler get'+str(e))
    
    def post(self):
        """ 这是post请求 接收消息,获取参数 """
        body = self.request.body
        # 返回的bodys是xml格式,通过ET转换为键值对格式,方便提取信息
        data = ET.fromstring(body)
        ToUserName = data.find('ToUserName').text
        FromUserName = data.find('FromUserName').text
        MsgType = data.find('MsgType').text
        # 如果发送的是消息请求,判断是文字还是语音,因为我们取发送的内容位置不一样
        if MsgType == 'text' or MsgType == 'voice':
            try:
                MsgId = data.find("MsgId").text
                if MsgType == 'text':
                    Content = data.find('Content').text  # 文本消息内容
                elif MsgType == 'voice':
                    Content = data.find('Recognition').text  # 语音识别结果,UTF8编码
                # 调用回复函数判断接受的信息,然后返回对应的内容
                reply_content=receive_msg(Content)
                CreateTime = int(time.time())
                # 调用回复信息封装函数,要指定用户,时间和回复内容
                out = reply_text(FromUserName, ToUserName, CreateTime, reply_content)
                self.write(out)
            except Exception as e:
                logger.error('wxStartHandler post'+str(e))
        # 如果接收的是事件,我们也要处理
        elif MsgType == 'event':
            try:
                Event = data.find('Event').text
                Event_key = data.find('EventKey').text
                CreateTime = int(time.time())
                # 判断事件,并返回内容
                reply_content = receive_event(Event,Event_key)
                if reply_content:
                    out = reply_text(FromUserName, ToUserName, CreateTime, reply_content)
                    self.write(out)
            except Exception as e:
                logger.error('wxStartHandler post'+str(e))

class pageHandler(tornado.web.RequestHandler):
    '''页面跳转控制路由'''
    wx_config = WxConfig()
    '''微信网页授权server'''
    wx_author_server = WxAuthorServer()
    def get(self, flag):
        try:
            if flag == '/wxauthor':
                '''微信网页授权'''
                code = self.get_argument('code')
                state = self.get_argument('state')
                # 获取重定向的url
                redirect_url = self.wx_config.wx_menu_state_map[state]
                if code:
                    # 通过code换取网页授权access_token
                    data = self.wx_author_server.get_auth_access_token(code)
                    openid = data['openid']
                    if openid:
                        # 跳到自己的业务界面
                        self.redirect(redirect_url)
                    else:
                        # 获取不到openid
                        logger.error('获取不到openid')
            # 如果请求的是airkiss页面
            elif flag == '/airkiss':
                self.render('../page/airkiss.html')
            elif flag == '/test':
                self.render('../page/test.html')
        except Exception as e:
            logger.error('pageHandler post'+str(e))

class getSignHandler(tornado.web.RequestHandler):
    """返回js-sdk签名数据"""
    wx_config = WxConfig()
    wx_token_cache = TokenCache()
    def get(self):
        # 调用微信js-sdk接口功能 需要签名
        sign=get_js_sdk_sign('%s/wx/page/airkiss'% self.wx_config.AppHost)
        sign['appId']=self.wx_config.AppID
        self.write(sign)

8、wxconfig.py

class WxConfig(object):
    """微信开发--基础配置"""
    # 测试账号
    AppID = 'wxxxxxxxxxx'
    AppSecret = '6xxxxxxxxxxxxxxxxxxx'

    """微信网页开发域名"""
    AppHost = 'https://www.f203.online'

    '''获取access_token接口'''
    get_access_token_url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s' % (AppID, AppSecret)

    '''自定义菜单创建接口'''
    menu_create_url = 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token='

    '''自定义菜单查询接口'''
    menu_get_url = 'https://api.weixin.qq.com/cgi-bin/menu/get?access_token='

    '''自定义菜单删除接口'''
    menu_delete_url = 'https://api.weixin.qq.com/cgi-bin/menu/delete?access_token='

    '''微信公众号菜单映射页面,参数是page/后面的'''
    wx_menu_state_map = {
        'airkiss': '%s/wx/page/airkiss'% AppHost,
        'test': '%s/wx/page/test'% AppHost
    }

 9、wxcache.py

# 缓存 access_token 和 jsapi_ticket,并且即使更新,防止过期
# access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。
# 开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时
# jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取
# 我们用redis去存数据,并设置过期时间
import redis
from wxlogger import logger

class BaseCache(object):
    """缓存类父类"""
    _host = '127.0.0.1'
    _port = 6379
    _database = 0
    _password = ''

    @property
    def redis_ctl(self):
        """redis控制句柄,就是连接对象"""
        redis_ctl = redis.Redis(host=self._host, port=self._port, db=self._database, password=self._password)
        return redis_ctl

class TokenCache(BaseCache):
    """微信token缓存"""
    _expire_access_token = 7200  # 微信access_token过期时间, 2小时
    _expire_js_token = 7200   # 微信jsapi_ticket, 过期时间, 7200秒

    def set_access_cache(self, key, value):
        """添加微信access_token验证相关redis"""
        self.redis_ctl.set(key, value)
        # 设置过期时间
        self.redis_ctl.expire(key, self._expire_access_token)
        logger.info('更新了 access_token')

    def set_js_cache(self, key, value):
        """添加网页授权相关redis"""
        self.redis_ctl.set(key, value)
        # 设置过期时间
        self.redis_ctl.expire(key, self._expire_js_token)
        logger.info('更新了 js_token')

    def get_cache(self, key):
        """获取redis"""
        try:
            v = (self.redis_ctl.get(key)).decode('utf-8')
            return v
        except Exception as e:
            logger.error('wxcache'+str(e))
            return None

10、wxauthorize.py

from wxconfig import WxConfig
from urllib import parse
from wxlogger import logger
import requests
import json
""" 用于拦截 对页面的请求 提取用户信息"""
class WxAuthorServer(object):
    """微信网页授权server"""

    """对与请求连接进行重定向,获取用户信息进行网页授权"""
    redirect_uri = '%s/wx/page/wxauthor' % WxConfig.AppHost
    """
    应用授权作用域
    snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)
    snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
    """
    SCOPE = 'snsapi_base'

    """通过code换取网页授权access_token"""
    get_access_token_url = 'https://api.weixin.qq.com/sns/oauth2/access_token?'

    """拉取用户信息"""
    get_userinfo_url = 'https://api.weixin.qq.com/sns/userinfo?'


    def get_code_url(self, state):
        """获取code的url"""
        _dict = {'redirect_uri': self.redirect_uri}
        redirect_uri = parse.urlencode(_dict)
        author_get_code_url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&%s&response_type=code&scope=%s&state=%s#wechat_redirect' % (WxConfig.AppID, redirect_uri, self.SCOPE, state)
        return author_get_code_url

    def get_auth_access_token(self, code):
        """通过code换取网页授权access_token"""
        try:
            url = self.get_access_token_url + 'appid=%s&secret=%s&code=%s&grant_type=authorization_code' % (WxConfig.AppID, WxConfig.AppSecret, code)
            r = requests.get(url)
            if r.status_code == 200:
                json_res = json.loads(r.text)
                if 'access_token' in json_res.keys():
                    return json_res
                elif 'errcode' in json_res.keys():
                    errcode = json_res['errcode']
                    logger.error('通过code换取网页授权access_token:'+errcode)
        except Exception as e:
            logger.error('get_auth_access_token:'+str(e))

11、test.html

<!DOCTYPE HTML>
<html>
<!-- 这是一个标准的app页面 -->
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, initial-scale=1.0, width=device-width" />
    <meta name="format-detection" content="telephone=no, email=no, date=no, address=no">
    <title>自定义页面-test</title>
    <style>
    </style>
</head>

<body>
    <div>这是一个测试页</div>
</body>
<script type="text/javascript">
</script>

</html>

12、airkiss.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, initial-scale=1.0, width=device-width" />
    <meta name="format-detection" content="telephone=no, email=no, date=no, address=no">
    <title>调用微信接口页面-airkiss</title>
    <style>
    </style>
</head>

<body>
    <div>即将自动跳转。。。</div>
</body>
<script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.f203.online/wx/sign');
xhr.send(null);
xhr.onload = function(e) {
    if (xhr.status === 200) {
        sign=JSON.parse(xhr.responseText)
        wx.config({
            beta: true, //开启内测接口调用,注入wx.invoke方法
            debug: false, //关闭调试模式
            appId: sign['appId'], //AppID
            timestamp: sign['timestamp'], //时间戳
            nonceStr: sign['nonceStr'], //随机串
            signature: sign['signature'], //js-sdk签名
            jsApiList: [
                // 所有要调用的 API 都要加到这个列表中
                'configWXDeviceWiFi'
            ]
        });
        wx.ready(function() {
            // 在这里调用 API
            wx.invoke('configWXDeviceWiFi');
        });
        wx.error(function(res) {
            alert("配置出错");
        });
    } else {
        alert('请求签名失败!');
    }
}
xhr.onerror = function(e) {
    alert('请求失败'+e)
}
</script>

</html>

 接下来就是分析了,奉上测试号供大家观看3月15号后会关闭

现在公众号可以用了 我前几天更新了域名导致以前的域名没法使用 有些网页打不开

在实际运行中我将网页授权去掉了 直接进行页面跳转

python 公众号应答 python项目公众号_tornado_03