需求:公司需要開發微信公衆號,並且和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和二維碼,太醜陋了。
至此我們就完成了,生成專屬二維碼的整個流程和代碼。
第一次在這裏發博客,如果有錯誤歡迎大家指出來。如果幫助了大家,希望大家給個贊。