需求:公司需要开发微信公众号,并且和h5无缝对接。由于以前都是运营人员直接在微信公众平台进行编辑的,就从没考虑如何做。前几天运营人员有个需求,也就是我们平日在别人公众号里点击生成专属二维码,现在要求统计谁生成的二维码,谁通过此二维码进行的扫码关注。
小公司,开发2人,原本想直接在网上找一个python的模块,import即可,想到今后要扩展功能会不会很麻烦。就自己干了。(既然微信开发的api,如果很复杂的话,公众号就不会这么火了。)
思路:微信平台就类似中间件, 我们的服务器,以及用户之间进行三角恋的变态关系。
1:和微信平台建立信赖关系。就是配置咱们的服务器和微信的连接。
2:微信事件推送,咱们服务器收到后,进行反馈。(微信有5秒等待时间)
3:调用微信二维码生成接口。(参数二维码)
4:利用python qrcode生成参数二维码,用PIL 将二维码图片和漂亮的背景图片进行合并,paste到设计的指定位置。
4:将图片上传至微信素材
5:将消息发给用户。
整个流程没有任何难点,难点就是特么文档接口之间毫无联系,查起来效率老低了。不知为何市场上却那么多以此谋生的企业???(你只需要最多一天的时间基本搞定所有这些东西)
废话不设了,
1:https://mp.weixin.qq.com/wiki 在文档中点击 接入指南。
token自己输入一个你喜欢并且保密的字符串。
当配置号url,token,EncodingAESKey 我们需要做的就是用我们的服务器在此url链接下返回微信想要的数据。(目的告诉微信相信我哦),这一步通过后,才能进行下面的步骤。
所以按照文档要求:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
ajaxLogger = logging.getLogger('ajax')
@csrf_exempt
def wechat_message_views(request):
result = {}
result["title"] = "微信消息"
if request.method == 'GET':
if request.GET.has_key("signature"):
signature = request.GET["signature"]
timestamp = request.GET["timestamp"]
nonce = request.GET["nonce"]
echostr = request.GET["echostr"]
token = "你的touken"
data_list = [token, timestamp, nonce]
data_list.sort()
weixin_sha1 = hashlib.sha1()
weixin_sha1.update("".join(data_list))
weixin_sha1 = weixin_sha1.hexdigest()
if weixin_sha1 == signature:
response = HttpResponse(echostr)
ajaxLogger.info("成功")
else:
response = HttpResponse("403")
ajaxLogger.info("非法")
else:
ajaxLogger.info(request.get_host())
response = HttpResponse("ok")
return response
return what_you_want_do(request)
其中,@csrf_exempt很重要,不然就403了。
接入成功。
2:接收微信事件推送。
(1)关注和取关事件推送
(2)扫描带参数二维码事件
就他两个了,最简单暴力的方法就是直接分析微信发送的xml格式。根据xml内容进行函数执行。(这里你可以用高级的python语法就执行)
在我们的服务器url 接口函数那里,我们的 what_you_want_do函数需要进行事件判读,然后分发。你可以用策略模式等高级方法去完善。咱们直接if else…………(记得有仁熊说过,有的人写了一辈子代码,永远的if else)
from xml.dom.minidom import parseString
def weixin_deal_xml(nodes,key):
try:
node_data = nodes.getElementsByTagName(key)
if node_data:
return node_data[0].childNodes[0].data
else:
return []
except Exception, e:
ajaxLogger.error( "解析XML错误 : %s"%str(e) )
return []
def what_you_want_do(request):
#先要验证request,方法和上面get一样,咱也要知道request是否来自微信
xml_result = request.body
try:
nodes = parseString(xml_result).documentElement
except Exception, e:
ajaxLogger.error("报错啦,sb %s" %str(e))
return HttpResponse("403")
#研究发现微信的返回参数都有的,就这么干了,最好是封装成函数,说不定今后有变化
msg_type = weixin_deal_xml(nodes, "MsgType")
user_open_id = weixin_deal_xml(nodes, "FromUserName")
wechat_pub_id = weixin_deal_xml(nodes, "ToUserName")
create_time = weixin_deal_xml(nodes, "CreateTime")
if not msg_type:
return HttpResponse("")
if not user_open_id:
return HttpResponse("")
#此处开始处理 事件推送,根据事件推送类型,去处理
if msg_type == "event":
event = weixin_deal_xml(nodes, "Event")
eventkey = weixin_deal_xml(nodes, "EventKey")
return deal_wechat_event(event, eventkey, user_open_id, wechat_pub_id, create_time)
else:
pass
收到不同微信服务器的事件推送,理论我们都应该就行xml的回复。如果是
VIEW : 视图跳转,咱们可以不返回,有需要后台存储一下用户的点击行为
CLICK :(这里参数二维码生成一定要是CLICK事件)。即在生成menu(https://api.weixin.qq.com/cgi-bin/menu/create?access_token={ACCESS_TOKEN})时,一定要将生成专属二维码设置成click。类似 {
“type”:”click”,
“name”:u”邀请好友”,
“key”:”V1001_你的_CODE_KEY”
}
此时,咱们做接收click事件,并通过key来判断是哪一个,然后返回相应的函数。
由于咱们处理生成二维码,还要进行和微信服务器素材的交互行为,但事件推送等待时间有限。咱们采用异步处理模式(gearman处理),先发一个消息提示用户,让用户等待一下,正在生成中。如:
即what_you_want_do函数,不论收到来自微信的任何消息,都应该返回一个xml消息。这里我们先返回一段话给用户。
根据微信的消息格式,这里一定要注意,微信没有在文档中进行说明,消息必须回复。真是坑爹,一定要回他哦。
import time
def response_to_wechat(touser, fromuser, text_content):
data = "<xml><ToUserName><![CDATA[%s]]></ToUserName>\
<FromUserName><![CDATA[%s]]></FromUserName>\
<CreateTime>%s</CreateTime>\
<MsgType><![CDATA[%s]]></MsgType>\
<Content><![CDATA[%s]]></Content>\
</xml>"%(touser,
fromuser,
int(time.time()),
"text",
text_content
)
return HttpResponse(data, content_type="application/xml")
调用response_to_wechat 发送一段等待的话给他,在这个之前调用异步生成参数二维码接口。
def deal_wechat_event(event, eventkey, user_open_id, wechat_pub_id, create_time):
# 点击菜单事件
if event == "CLICK":
if eventkey == "V1001_你的_CODE_KEY":
params = {
"user_open_id":user_open_id,
"eventkey":eventkey,
"wechat_pub_id":wechat_pub_id,
"create_time":create_time
}
call_command('gearman_submit_job','worker_name', json.dumps(params),foreground=False)
text_content = "协力正为您生成邀请二维码,等待5秒左右即可收到。"
return response_to_wechat(user_open_id, wechat_pub_id, text_content)
(上面的if ,else 都可以通过python技巧,以及设计模式进行更好的代码维护。自己进行吧。)
异步的worker,我们在里面定义处理函数。
函数一:生成参数二维码,需求里我们要存储生成二维码的用户的信息。(此用户信息即为参数key。当其他人通过扫描生成的二维码时,我们要从推送的信息中查询微信返回的key是哪个用户。)
文档
我们使用临时二维码,临时素材。(因为永久的生成的个数太少了)
def create_scene_qrcode(user_open_id):
scene_id = 100000000 #(每个用户不一样,你自己需要进行改变,如自增)
# 获取参数二维码 url。自己进行二维码图片生成
url = wechat_qr_imge_url(scene_id)
image_data = create_wechat_qrcode(url)
media_id = post_picture_to_weixin(image_data)
create_time = int(time.time())
send_user_message(user_open_id, media_id)
#带参数二维码
def wechat_qr_imge_url(final_scene_id):
ACCESS_TOKEN = get_accesstoken()
url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={ACCESS_TOKEN}".format(ACCESS_TOKEN=ACCESS_TOKEN)
data = {
"expire_seconds": 1000,
"action_name": "QR_SCENE",
"action_info": {"scene":
{"scene_id": final_scene_id}
}
}
data = json.dumps(data)
try:
data_result = requests.post(url,data.encode('utf8'))
result = data_result.json()
ajaxLogger.info(str(result))
if result.has_key("ticket"):
ticket = result["ticket"]
url = result["url"]
return url
except Exception, e:
ajaxLogger.error("生成专属参数二维码 {error}".format(error=str(e)))
# 获取二维码ticket后,开发者可用ticket换取二维码图片,也可以将返回的url自行处理,咱们自此处理
#{"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm
# 换取二维码图片
# https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
get_accesstoken方法,实现了使用redis存储token,因为token微信每天获取次数有限制,并且有过期时间。所以咱们使用reids expire。将过期时间和 token过期一致即可。非常简单
create_wechat_qrcode 生成合并图片。
此函数目的就是1:生成二维码,2:将二维码和美丽的背景图片进行合并。
from PIL import Image
import qrcode
from io import StringIO, BytesIO
def create_wechat_qrcode(params):
#参数可自行调整
qr = qrcode.QRCode(
version=2,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=1
)
#二维码填充内容
qr.add_data(params)
qr.make(fit=True)
img = qr.make_image()
img = img.convert("RGBA")
#打开背景图片
icon = Image.open("你的背景图片地址.png")
#根据设计将二维码填充到制定位置
icon.paste(img, ("位置坐标", "位置坐标"), img)
buf = BytesIO()
#生成二进制文件,直接发给微信
icon.save(buf,format="PNG")
file_content = buf.getvalue()
return file_content
此函数将图片上传至微信的临时素材
post_picture_to_weixin
# 上传图消息素材
def post_picture_to_weixin(rawimg):
ACCESS_TOKEN = get_accesstoken()
url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token={ACCESS_TOKEN}&type={TYPE}".format(ACCESS_TOKEN=ACCESS_TOKEN, TYPE="image")
file_name = str(time.time()).split(".")[0] + 'tmp.png'
#微信文档有文件上传时的要求
files = { 'media' : (file_name, rawimg,'image/png')}
res = requests.post(url, files=files)
result_data = res.json()
#media_id 通过media_id给微信用户发送图片消息
if result_data.has_key("media_id"):
media_id = result_data["media_id"]
return media_id
最后哦,send_user_message给用户主动发消息。(由于我们异步处理,xml格式的回复已经发给用户,现在就得主动发消息给用户。)
def send_user_message(OPENID, MEDIA_ID, msgtype="image"):
ACCESS_TOKEN = get_accesstoken()
url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={ACCESS_TOKEN}".format(ACCESS_TOKEN=ACCESS_TOKEN)
data = {
"touser":OPENID,
"msgtype":msgtype,
"image":
{
"media_id":MEDIA_ID
}
}
if msgtype == "text":
data["text"] = {
"content":"Hello World"
}
request_weixin(url, data)
def request_weixin(url, data):
# 有些中文,以及json格式中的,必须使用ensure_ascii=False,不然有时会报错
data = json.dumps(data, ensure_ascii=False)
try:
data_result = requests.post(url,data.encode('utf8'))
result = data_result.json()
print result
# ajaxLogger.info(str(result))
except Exception, e:
print e
# ajaxLogger.error("设置客服失败 {error}".format(error=str(e)))
哈哈没有美工,就拿logo放在中间啦,结果logo和二维码,太丑陋了。
至此我们就完成了,生成专属二维码的整个流程和代码。
第一次在这里发博客,如果有错误欢迎大家指出来。如果帮助了大家,希望大家给个赞。