订阅号、服务号
- 微信公众号主要面向的是名人、政府、媒体、企业等机构的,它又分为订阅号和服务号
- 订阅号和服务号都有订阅和普通的区分,订阅的账号其开通的接口权限会更多些
- 订阅号:主要偏向于为用户传达资讯,(功能类似报纸杂志,为用户提供新闻信息或娱乐趣事),每天可群发1条消息,消息是在订阅账号列表里的
- 服务号:主要偏向于服务交互(功能类似12315,114,银行,提供绑定信息,服务交互),每月可群发4条消息,它的消息是在用户的聊天列表的
- 注册地址
微信公众号开发场景
- 公众号的消息自动回复想做的智能一些,类似于iphone的Siri,例如粉丝发送“今天的北京天气”到公众号,回复粉丝信息时要按照特定时间特定城市给予反馈;
- 公众号内嵌的网页需要获取浏览用户的微信头像、昵称、当前定位等信息
- 以上两种均是微信公众平台预定义功能无法完成的,所以就需要公众号开发了
扩展应用模型
-
就是在用一台我们自己的服务器对接微信的服务器,微信服务器把用户发来的消息转接到我们的服务器,然后我们的服务器在经过处理后,把需要恢复的东西交给微信服务器,由其发送回给微信用户
接入微信公众平台
-
可以用微信的测试平台来进行开发测试
-
首先,我们得先验证服务器的有效性
-
在测试平台中,有个接口信息配置信息
URL http://服务器的IP/wechat Token fxh # 用于验证的
URL得注意的是,端口只能是80,不写则默认的是80
-
点击提交按钮时,微信服务器会向上面填写的ip发送一个GET请求,其携带四个参数
参数 描述 signature w微信加密签名,signature结合开发者填写的token参数和请求中timestamp参数、noce参数 timestamp 时间戳 nonce 随机数 echostr 随机字符串 -
开发者通过检验signature对请求进行校验,若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败
-
校验流程:
- 将token、timestamp、nonce三个参数进行字典序排序
- 将三个参数字符串拼接成一个字符串进行sha1加密
- 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
from flask import Flask,request,abort import hashlib app = Flask(__name__) @app.route('/wechat') def wechat(): # 设置token token = 'python' # 获取参数 data = request.args signature = data.get('signature') timestamp = data.get('timestamp') nonce = data.get('nonce') echostr = data.get('echostr') # 校验参数 if not all([signature, timestamp, nonce]): abort(400) # 按照微信的流程进行计算签名 temp = [timestamp,nonce,token] # 排序 temp.sort() # 拼接字符串 temp_str = ''.join(temp) # 进行sha1加密,得到正确的签名值 sign = hashlib.sha1(tmp_str).hexdigest() # hexdigest()获取加密后的结果 if signature == sign: return echostr else: return abort(403) if __name__ == '__main__': app.run(port=8000,debug=True)
-
-
把代码运行在我们的服务器上面,然后平台上点击提交
-
注意:因为我之前在服务器上用Nginx部署了项目,所以当微信服务器访问时会不成功,导致点击提交会提示配置失败,这时就得去修改下nginx的配置文件,在监听的80端口中加上
location /wechat { proxy http://127.0.0.1:8000; }
公众号接收与发送消息
-
用户向公众号发送消息时,公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID
- 普通消息的类别有:文本消息、图片消息、语音消息、视频消息、小视频消息、地理位置消息、链接消息
-
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上
格式如下:
<xml> <ToUserName><![CDATA[gh_866835093fea]]></ToUserName> <FromUserName><![CDATA[ogdotwSc_MmEEsJs9-ABZ1QL_4r4]]></FromUserName> <CreateTime>1478317060</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[你好]]></Content> <MsgId>6349323426230210995</MsgId> </xml>
参数 描述 ToUserName 开发者微信号 FromUserName 发送方账号(一个OpenID) CreateTime 消息创建时间 MsgType text(文本消息的类型) Content 文本消息内容 MsgID 消息ID,64位整型 -
xmltdict模块
- 该模块是用来帮助我们更方便的模块,有两个常用的方法parse、unparse
xmltodict.parse()
:将XML数据转为Python字典dictxmltodict.unparse()
:将dict数据转换为XML数据
- 该模块是用来帮助我们更方便的模块,有两个常用的方法parse、unparse
-
发送文本消息
-
无非就是按上面的xml的格式把数据发送回去
if signature != sign: # 表示请求不是微信发的 abort(403) else: # 表示是微信发送的请求 if request.method == "GET": # 表示是第一次接入微信服务器的验证 echostr = request.args.get("echostr") if not echostr: abort(400) return echostr elif request.method == "POST": # 表示微信服务器转发消息过来 xml_str = request.data if not xml_str: abort(400) # 对xml字符串进行解析 xml_dict = xmltodict.parse(xml_str) xml_dict = xml_dict.get("xml") # 提取消息类型 msg_type = xml_dict.get("MsgType") if msg_type == "text": # 表示发送的是文本消息 # 构造返回值,经由微信服务器回复给用户的消息内容 resp_dict = { "xml": { "ToUserName": xml_dict.get("FromUserName"), "FromUserName": xml_dict.get("ToUserName"), "CreateTime": int(time.time()), "MsgType": "text", "Content": xml_dict.get("Content") } } else: resp_dict = { "xml": { "ToUserName": xml_dict.get("FromUserName"), "FromUserName": xml_dict.get("ToUserName"), "CreateTime": int(time.time()), "MsgType": "text", "Content": "i love u" } } # 将字典转换为xml字符串 resp_xml_str = xmltodict.unparse(resp_dict) # 返回消息数据给微信服务器 return resp_xml_str
-
接受其他普通消息
-
图片消息
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[image]]></MsgType> <PicUrl><![CDATA[this is a url]]></PicUrl> <MediaId><![CDATA[media_id]]></MediaId> <MsgId>1234567890123456</MsgId> </xml>
参数 描述 回复时是否需要 ToUserName 开发者微信号 是 FromUserName 发送方帐号(一个OpenID) 是 CreateTime 消息创建时间 (整型) 是 MsgType image 是 PicUrl 图片链接 是 MediaId 图片消息媒体id,可以调用多媒体文件下载接口拉取数据 是 MsgId 消息id,64位整型 -
视频消息
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1357290913</CreateTime> <MsgType><![CDATA[shortvideo]]></MsgType> <MediaId><![CDATA[media_id]]></MediaId> <ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId> <MsgId>1234567890123456</MsgId> </xml>
参数 描述 MsgType 视频为video(小视频shortvideo) MediaId 视频消息媒体id,可以调用多媒体文件下载接口拉取数据 ThumbMediaId 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据 -
回复
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[video]]></MsgType> <Video> <MediaId><![CDATA[media_id]]></MediaId> <Title><![CDATA[title]]></Title> <Description><![CDATA[description]]></Description> </Video> </xml>
Title:视频消息的标题,并不是必须传
Description:视频消息的描述,不是必须
-
-
语音消息
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1357290913</CreateTime> <MsgType><![CDATA[voice]]></MsgType> <MediaId><![CDATA[media_id]]></MediaId> <Format><![CDATA[Format]]></Format> <MsgId>1234567890123456</MsgId> </xml>
参数 描述 MsgType 语音为voice MediaId 语音消息媒体id,可以调用多媒体文件下载接口拉取数据 Format 语音格式,如amr,speex等
关注、取消关注
-
用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> </xml>
- Event:事件类型,subscribe(订阅)、unsubscribe(取消订阅)
微信网页授权
-
我们要实现一个微信内网页,通过微信访问网页时,网页会展示微信用户的个人信息。因为涉及到用户的个人信息,所以需要有用户授权才可以。当用户授权后,我们的网页服务器(开发者服务器)会拿到用户的“授权书”(code),我们用这个code向微信服务器领取访问令牌(accecc_token)和用户的身份号码(openid),然后凭借access_token和openid向微信服务器提取用户的个人信息
- 第一步:用户同意授权,获取code
- 第二步:通过code换取网页授权access_token
- 第三步:拉取用户信息(需scope为 snsapi_userinfo)
-
获取流程
-
设置网页授权回调域名
-
让用户访问下面链接,同意授权,获取code
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
参数 是否必读 说明 appid 是 公众号唯一标识 redirect_url 是 授权后重定向的回调链接地址,请使用urlencode对链接进行处理 response_type 是 返回类型,请填写code scope 是 应用授权作用域,snsapi_base(不弹出授权页面,直接跳转,只能获取用户的openid),snsapi_userinfo(弹出授权页面,可通过openid拿到昵称、性别、所在地)。并且,即使在未授权的情况下,只要用户授权,也能获取其信息 state 否 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 #wechat_redirect 是 无论直接打开还是做页面302重定向时候,必须带此参数 -
通过code换取网页授权access_token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
code:填写上一步获取的code
grant_type:就填写authorization_code
-
返回的json数据
{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" }
-
-
获取用户信息,请求
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
-
代码
WECHAT_APPID = "wx67755f74fxfeef78" WECHAT_APPSECRET = "aaf6dbca95a012895eb570f0ba549ee5" @app.route("/wechat8000/index") def index(): """让用户通过微信访问的网页页面视图""" # 从微信服务器中拿去用户的资料数据 # 1. 拿去code参数 code = request.args.get("code") if not code: return u"确实code参数" # 2. 向微信服务器发送http请求,获取access_token url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code" \ % (WECHAT_APPID, WECHAT_APPSECRET, code) # 使用urllib2的urlopen方法发送请求 # 如果只传网址url参数,则默认使用http的get请求方式, 返回响应对象 response = urllib2.urlopen(url) # 获取响应体数据,微信返回的json数据 json_str = response.read() resp_dict = json.loads(json_str) # 提取access_token if "errcode" in resp_dict: return u"获取access_token失败" access_token = resp_dict.get("access_token") open_id = resp_dict.get("openid") # 用户的编号 # 3. 向微信服务器发送http请求,获取用户的资料数据 url = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN" \ % (access_token, open_id) response = urllib2.urlopen(url) # 读取微信传回的json的响应体数据 user_json_str = response.read() user_dict_data = json.loads(user_json_str) if "errcode" in user_dict_data: return u"获取用户信息失败" else: # 将用户的资料数据填充到页面中 return render_template("index.html", user=user_dict_data)
-