python微信公衆號生成專屬二維碼--你再也不用去求人了

需求:公司需要開發微信公衆號,並且和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和二維碼,太醜陋了。

至此我們就完成了,生成專屬二維碼的整個流程和代碼。
第一次在這裏發博客,如果有錯誤歡迎大家指出來。如果幫助了大家,希望大家給個贊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章