一、微信公众号开发基础
1.1 与公众号的消息会话1.2 与公众号内的网页交互1.2 搭建开发者环境
1.2.1 配置nginx环境1.2.2 云服务器与微信服务器建立连接
1.2.2.1 token与access_token1.2.2.2 建立连接 二、微信公众号部署AI对话机器人三、公众号回复其他类型消息
3.1 接口的概念3.2 使用接口进行开发3.2.1 实现元宵节猜灯谜 四.参考文献 一、微信公众号开发基础 1.1 与公众号的消息会话
在部署好一个微信公众号时,用户通过微信客户端向公众号发送文本消息并请求返回,这一过程实际上分为四个阶段:
1.用户向微信服务器发出请求
2.微信服务器向云服务器转发请求
3.云服务器接收请求并返回给云服务器响应消息
4.微信服务器再返回给用户回复消息
公众号的网页交互与消息会话刚好不同,它基于以下四个过程:
1.用户向网页服务器发出请求
2.网页服务器向微信服务器转发请求寻求用户信息
3.微信服务器接收请求并返回给网页服务器用户信息
4.网页服务器再返回给用户网页资源
开发者环境是指我们开发微信公众号的环境,与云服务器的运行环境相对应。微信官方为开发者提供了开发者文档,里面规定了开发微信公众号所使用的端口号(只能用80(http)和443(https)这两个,实际上微信公众号只支持80端口)。
按照开发者文档,我们首先需要在开发者环境下搭建开发环境:
nginx作为高性能的HTTP和反向代理web服务器,负责将每个用户账号从80端口跑到固定的端口,如从8001开始,8002,8003…以此类推。注意运行在哪个端口,微信服务器配置的URL对应的就是wechatXXXX。
首先在云服务器上安装nginx:
sudo apt-get install nginx
接下来在云服务器上打开/etc/nginx/sites-available, 使用vim编辑该目录下的default文件:
sudo vim /etc/nginx/sites-available/default
找到文件中的server部分,将
location / {XXXXX}
这一部分内容改为:
location /wechat8000 {proxy_pass_header Server;proxy_set_header Host $http_host;proxy_redirect off;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Scheme $scheme;proxy_pass http://127.0.0.1:8000;}
修改后的default文件(去掉部分注释):
### You should look at the following URL's in order to grasp a solid understanding# of Nginx configuration files in order to fully unleash the power of Nginx.# https://www.nginx.com/resources/wiki/start/# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/# https://wiki.debian.org/Nginx/DirectoryStructure## In most cases, administrators will remove this file from sites-enabled/ and# leave it as reference inside of sites-available where it will continue to be# updated by the nginx packaging team.## This file will automatically load configuration files provided by other# applications, such as Drupal or Wordpress、These applications will be made# available underneath a path with that package name, such as /drupal8.## Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.### Default server configuration#server {listen 80 default_server;listen [::]:80 default_server;root /var/www/html;index index.html index.htm index.nginx-debian.html;server_name _;location /wechat8000 {undefinedproxy_pass_header Server;proxy_set_header Host $http_host;proxy_redirect off;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Scheme $scheme;proxy_pass http://127.0.0.1:8000;}}# Virtual Host configuration for example.com## You can move that to a different file under sites-available/ and symlink that# to sites-enabled/ to enable it.##server {#listen 80;#listen [::]:80;##server_name example.com;##root /var/www/example.com;#index index.html;##location / {#try_files $uri $uri/ =404;#}#}
1.2.2 云服务器与微信服务器建立连接 1.2.2.1 token与access_token 这是两个东西。
1.Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性,完全是云服务器与微信服务器建立连接时的验证工具,可以无限次使用。
2.access_token的意义在于拿到用户在第三方平台的唯一的标识,这样就可以用于获取用户的nickname,头像,邮箱等其他信息。
3.为了保证微信服务器与云服务器连接的安全,微信官方规定每2个小时(7200s)需要刷新一次access_token,同时为了防止因为开发者程序的bug导致微信服务器资源耗尽,规定单个微信公众号每日获取access_token的上限为2000次,对于运行正常的公众号完全可以24h无限制与用户进行消息交互。
4.access_token分为两个种类,一种是普通的access_token,另一种是网页授权access_token。多个不同的进程独立的去获取这种普通access_token,就会导致有些接口没办法正常地调用相关的微信接口,因此最好使用中控服务器专门负责刷新access_token。
综前所述,我们需要在后端服务器上实现一个/wechat80XX的视图,基于这个视图就可以实现微信服务器和后端云服务器的通信:
接下来向服务器上传一个wechat.py文件,用于向微信服务器建立连接。上传方式可以是ftp,也可以是ssh远程传输(效果一样)。
wechat.py(记得修改token、填写APPID和APPSECRET):
# coding:utf-8from flask import Flask, request, abort, render_templateimport hashlibimport xmltodictimport timeimport urllib.request as urllib2import json# 微信的token令牌WECHAT_TOKEN = '你的token'WECHAT_APPID = '你的APPID'WECHAT_APPSECRET = '你的APPSECRET'app = Flask(__name__)@app.route("/wechat8000", methods=["GET", "POST"])def wechat(): """对接微信公众号服务器""" # 接收微信服务器发送的参数 signature = request.args.get("signature") timestamp = request.args.get("timestamp") nonce = request.args.get("nonce") # 校验参数 if not all([signature, timestamp, nonce]): abort(400) # 按照微信的流程进行计算签名 li = [WECHAT_TOKEN, timestamp, nonce] # 排序 li.sort() # 拼接字符串 tmp_str = "".join(li) # 进行sha1加密,得到正确的签名值 sign = hashlib.sha1(tmp_str.encode("utf-8")).hexdigest() # 将自己计算的签名值与请求的签名参数进行对比,如果相同,则证明请求来自微信 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["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@app.route('/wechat8000/index')def index(): """让用户通过微信访问的网页页面视图""" # 从微信服务器中拿取用户的资料数据 # 1、拿取code参数 code = request.args.get("code") if not code: return "缺失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 "获取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 "获取用户信息失败" else: # 将用户的资料数据填充到页面 return render_template("index.html", user=user_dict_data)if __name__ == '__main__': app.run(port=8000, debug=True)
通过xftp软件向服务器上传修改后的wechat.py文件:
云服务器上需要同步基于python的flask,这是一个轻量级web框架,先在服务器上安装flask:
pip install flask
查看端口号8000的开放情况(使用阿里云的朋友记得在阿里云的安全组配置上面开放80端口和8000端口的使用):
netstat -apn | grep 8000
进入虚拟环境:
conda activate paddle_env
前面编写写的GET和POST方法向微信服务器请求access_token,运行wechat.py文件,与微信服务器建立连接,获取accexx_token:
在URL输入框里的IP后面不用添加端口号,因为一方面微信公众号http协议只支持80端口,另一方面我们先让微信服务器把消息转发到云服务器的nginix上,然后再交给服务器,注意URL一定要填http而不是https,否则不成功:
提交后显示成功,然后点击启用:
二、微信公众号部署AI对话机器人服务器运行AI部署代码:
import paddlenlpimport utilsfrom utils import select_sumfrom utils import post_process_sumfrom flask import Flask, request, abort, render_templateimport hashlibimport xmltodictimport timeimport urllib.request as urllib2import json# 微信的token令牌WECHAT_TOKEN = '你的token'WECHAT_APPID = '你的APPID'WECHAT_APPSECRET = '你的SECRET'model = paddlenlp.transformers.UNIMOLMHeadModel.from_pretrained('data126898')tokenizer = paddlenlp.transformers.UNIMOTokenizer.from_pretrained('unimo-text-1.0')model.eval()num_return_sequences = 8app = Flask(__name__)@app.route("/wechat8000", methods=["GET", "POST"])def wechat(): """对接微信公众号服务器""" # 接收微信服务器发送的参数 signature = request.args.get("signature") timestamp = request.args.get("timestamp") nonce = request.args.get("nonce") # 校验参数 if not all([signature, timestamp, nonce]): abort(400) # 按照微信的流程进行计算签名 li = [WECHAT_TOKEN, timestamp, nonce] # 排序 li.sort() # 拼接字符串 tmp_str = "".join(li) # 进行sha1加密,得到正确的签名值 sign = hashlib.sha1(tmp_str.encode("utf-8")).hexdigest() # 将自己计算的签名值与请求的签名参数进行对比,如果相同,则证明请求来自微信 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["xml"] # 提取消息类型 msg_type = xml_dict.get("MsgType") if msg_type == "text": # 表示发送的是文本消息 # 构造返回值,经由微信服务器回复给用户的消息内容 source_text = xml_dict.get("Content") # 由于训练时间较长,这里我们直接读训练好的模型 inputs = source_text inputs_ids = tokenizer.gen_encode(inputs, return_tensors=True, add_start_token_for_decoding=True, return_position_ids=True) # 调用生成api并指定解码策略为beam_search outputs, scores = model.generate(**inputs_ids, decode_strategy='beam_search', num_beams=8, num_return_sequences=num_return_sequences) # 调用生成api并指定解码策略为Sampling,不同策略的效果不同哦。 r = '' for i in range(num_return_sequences): r = r+'{} 上联: {} 下联:{}'.format(i, inputs, ''.join(post_process_sum(outputs[i].numpy(), tokenizer)[1]))+'n' resp_dict = { "xml": { "ToUserName": xml_dict.get("FromUserName"), "FromUserName": xml_dict.get("ToUserName"), "CreateTime": int(time.time()), "MsgType": "text", "Content": r } } 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) resp_xml_str = resp_xml_str+"我爱你" # 返回消息数据给微信服务器 return resp_xml_str@app.route('/wechat8000/index')def index(): """让用户通过微信访问的网页页面视图""" # 从微信服务器中拿取用户的资料数据 # 1、拿取code参数 code = request.args.get("code") if not code: return "缺失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 "获取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 "获取用户信息失败" else: # 将用户的资料数据填充到页面 return render_template("index.html", user=user_dict_data)if __name__ == '__main__': app.run(port=8000, debug=True)
服务器运行结果:
公众号效果:
接口是指微信服务器提供的供开发者可以直接使用的API(Application program Interface,应用程序接口),开发者在使用接口时需要定义和接口相同名字的函数,这被视为双方达成的一致协议。
相关接口的使用次数都是有上限的,目的为了防止公众号的程序错误而引发微信服务器负载异常,默认情况下,每个公众号调用接口都不能超过一定限制。
根据微信开发者文档的规定,要想使用微信提供的接口,必须先获得有效的access_token。
3.2.1 实现元宵节猜灯谜将程序挂在服务器上:
nohup python -u main_test.py > test.out 2>&1 &
上述命令关键词含义:
其中 0、1、2分别代表如下含义: 0 – stdin (standard input) 1 – stdout (standard output) 2 – stderr (standard error)nohup python -u main.py > test.out 2>&1 &nohup+最后面的& 是让命令在后台执行>out.log 是将信息输出到out.log日志中2>&1 是将标准错误信息转变成标准输出,这样就可以将错误信息输出到out.log 日志里面来。
这样即使是关闭Xshell等ssh连接软件也可以继续提供服务了。
四.参考文献https://blog.csdn.net/mrbcy/article/details/64533496