使用雲函數SCF+COS免費運營微信公衆号 - 新聞資(zī)訊 - 雲南小程序開發|雲南軟件開發|雲南網站(zhàn)建設-西山區知普網絡科技工作室

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不管是網站(zhàn),軟件還是小程序,都要直接或間接能為您産生價值,我們在追求其視覺表現的同時,更側重于功能的便捷,營銷的便利,運營的高效,讓網站(zhàn)成為營銷工具,讓軟件能切實提升企業(yè)内部管理水平和(hé)效率。優秀的程序為後期升級提供便捷的支持!

使用雲函數SCF+COS免費運營微信公衆号

發表時間:2020-9-30

發布人:葵宇科技

浏覽次數:57

是的,你(nǐ)沒聽錯,這一次我來帶大家直接上手運營微信公衆号。

震驚,Awesome,哼,我才不信捏,所謂無圖無真相 ~

效果展示

更多的體驗,可(kě)以關(guān)注我的微信公衆号:乂乂又又 (僅供測試,不要亂搞哈~)

嗯,這次我信了,快點教一下(xià)我吧,嘤嘤嘤~

操作步驟

在上一篇《使用SCF+COS快速開發全棧應用》教程中(zhōng),我們用騰訊雲無服務器(qì)雲函數 SCF 和(hé)對象存儲 COS 實現了一個(gè)後端雲函數,這個(gè)雲函數可(kě)以根據我們的請求返回對應的結果。

現在我們将嘗試在這個(gè)雲函數的基礎上解析微信 XML 消息,實現公衆号消息的自動(dòng)回複,關(guān)鍵詞回複,文(wén)字菜單等功能。

01

添加相關(guān)依賴

為了快速完成開發,這裡我們選擇 python 第三方開源庫 wechatpy 來接入微信公衆平台。

wechatpy 支持以下(xià)功能:

  1. 普通(tōng)公衆平台被動(dòng)響應和(hé)主動(dòng)調用 API

  2. 企業(yè)微信 API

  3. 微信支付 API

  4. 第三方平台代公衆号調用接口 API

  5. 小程序雲開發 API

可(kě)見功能是十分完整的,不僅支持普通(tōng)公衆平台主被動(dòng)調用,企業(yè)微信和(hé)微信支付,甚至還支持第三方平台代公衆号調用接口,拿來運營微信公衆号是十分綽綽有餘的~

由于騰訊雲函數的運行環境中(zhōng)缺少(shǎo)第三方庫,需要我們自己手動(dòng)上傳添加依賴,這裡我們需要添加的第三方依賴有:wechatpyoptionaldictxmltodict 以及 timeout_decorator

其中(zhōng) wechatpy 需要依賴 optionaldictxmltodicttimeout_decorator 是用來限制函數運行時長的,具體的依賴文(wén)件可(kě)以自行 pip 安裝後 copy 到雲函數項目根目錄,如(rú)上圖。

02

接入微信公衆号

這裡需要記下(xià)自己的 AppID、Token 和(hé) EncodingAESKey,消息加密方式建議選為安全模式。這個(gè)頁面先不要關(guān),一會兒上線發布好雲函數還需要過來再次修改配置。

03

編寫雲函數解析并回複微信公衆号消息

這一步可(kě)以直接參考 wechatpy 的官方文(wén)檔

地址:http://docs.wechatpy.org/zh_CN/master/quickstart.html#id2

Life is short, show me the code.

這裡我就直接上代碼了(原始業(yè)務代碼已略去,可(kě)以按照自己的需求開發)

import json
import timeout_decorator
from wechatpy.replies import ArticlesReply
from wechatpy.utils import check_signature
from wechatpy.crypto import WeChatCrypto
from wechatpy import parse_message, create_reply
from wechatpy.exceptions import InvalidSignatureException, InvalidAppIdException


# 是否開啟本地debug模式
debug = False


# 騰訊雲對象存儲依賴
if debug:
    from qcloud_cos import CosConfig
    from qcloud_cos import CosS3Client
    from qcloud_cos import CosServiceError
    from qcloud_cos import CosClientError
else:
    from qcloud_cos_v5 import CosConfig
    from qcloud_cos_v5 import CosS3Client
    from qcloud_cos_v5 import CosServiceError
    from qcloud_cos_v5 import CosClientError


# 配置存儲桶
appid = '66666666666'
secret_id = u'xxxxxxxxxxxxxxx'
secret_key = u'xxxxxxxxxxxxxxx'
region = u'ap-chongqing'
bucket = 'name'+'-'+appid


# 對象存儲實例
config = CosConfig(Secret_id=secret_id, Secret_key=secret_key, Region=region)
client = CosS3Client(config)


# cos 文(wén)件讀寫
def cosRead(key):
    try:
        response = client.get_object(Bucket=bucket, Key=key)
        txtBytes = response['Body'].get_raw_stream()
        return txtBytes.read().decode()
    except CosServiceError as e:
        return ""


def cosWrite(key, txt):
    try:
        response = client.put_object(
            Bucket=bucket,
            Body=txt.encode(encoding="utf-8"),
            Key=key,
        )
        return True
    except CosServiceError as e:
        return False


def getReplys():
    replyMap = {}
    replyTxt = cosRead('Replys.txt')  # 讀取數據
    if len(replyTxt) > 0:
        replyMap = json.loads(replyTxt)
    return replyMap


def addReplys(reply):
    replyMap = getReplys()
    if len(replyMap) > 0:
        replyMap[reply]='我是黑名單'
    return cosWrite('Replys.txt', json.dumps(replyMap, ensure_ascii=False)) if len(replyMap) > 0 else False




def delReplys(reply):
    replyMap = getReplys()
    if len(replyMap) > 0:
        replyMap.pop(reply)
    return cosWrite('Replys.txt', json.dumps(replyMap, ensure_ascii=False)) if len(replyMap) > 0 else False




# 微信公衆号對接
wecaht_id = 'xxxxxxxxxxxxxxx'
WECHAT_TOKEN = 'xxxxxxxxxxxxxxxxxxx'
encoding_aes_key = 'xxxxxxxxxxxxxxxxxxxxxx'


crypto = WeChatCrypto(WECHAT_TOKEN, encoding_aes_key, wecaht_id)


# api網關(guān)響應集成
def apiReply(reply, txt=False, content_type='application/json', code=200):
    return {
        "isBase64Encoded": False,
        "statusCode": code,
        "headers": {'Content-Type': content_type},
        "body": json.dumps(reply, ensure_ascii=False) if not txt else str(reply)
    }


def replyMessage(msg):
    txt = msg.content
    ip = msg.source
    print('請求信息--->'+ip+'%'+txt)  # 用來在騰訊雲控制台打印請求日志
    replysTxtMap = getReplys() # 獲取回複關(guān)鍵詞
    if '@' in txt:
        keys = txt.split('@')
        if keys[0] == '電影': #do something
            return
        if keys[0] == '音樂(yuè)': #do something
            return
        if keys[0] == '下(xià)架': #do something
            return
        if keys[0] == '上架': #do something
            return
        if keys[0] == '回複': #do something
            return
        if keys[0] == '删除': #do something
            return
    elif txt in replysTxtMap.keys(): # 如(rú)果消息在回複關(guān)鍵詞内則自動(dòng)回複
        return create_reply(replysTxtMap[txt], msg)
    return create_reply("喵嗚 ?'ω'?", msg)


def wechat(httpMethod, requestParameters, body=''):
    if httpMethod == 'GET':
        signature = requestParameters['signature']
        timestamp = requestParameters['timestamp']
        nonce = requestParameters['nonce']
        echo_str = requestParameters['echostr']
        try:
            check_signature(WECHAT_TOKEN, signature, timestamp, nonce)
        except InvalidSignatureException:
            echo_str = 'error'
        return apiReply(echo_str, txt=True, content_type="text/plain")
    elif httpMethod == 'POST':
        msg_signature = requestParameters['msg_signature']
        timestamp = requestParameters['timestamp']
        nonce = requestParameters['nonce']
        try:
            decrypted_xml = crypto.decrypt_message(
                body,
                msg_signature,
                timestamp,
                nonce
            )
        except (InvalidAppIdException, InvalidSignatureException):
            return
        msg = parse_message(decrypted_xml)
        if msg.type == 'text':
            reply = replyMessage(msg)
        elif msg.type == 'image':
            reply = create_reply('哈? ???\n好端端的,給我發圖片幹啥~', msg)
        elif msg.type == 'voice':
            reply = create_reply('哈? ???\n好端端的,給我發語音幹啥~', msg)
        else:
            reply = create_reply('哈? ???\n搞不明白你(nǐ)給我發了啥~', msg)
        reply = reply.render()
        print('返回結果--->'+str(reply))  # 用來在騰訊雲控制台打印請求日志
        reply = crypto.encrypt_message(reply, nonce, timestamp)
        return apiReply(reply, txt=True, content_type="application/xml")
    else:
        msg = parse_message(body)
        reply = create_reply("喵嗚 ?'ω'?", msg)
        reply = reply.render()
        print('返回結果--->'+str(reply))  # 用來在騰訊雲控制台打印請求日志
        reply = crypto.encrypt_message(reply, nonce, timestamp)
        return apiReply(reply, txt=True, content_type="application/xml")




@timeout_decorator.timeout(4, timeout_exception=StopIteration)
def myMain(httpMethod, requestParameters, body=''):
    return wechat(httpMethod, requestParameters, body=body)




def timeOutReply(httpMethod, requestParameters, body=''):
    msg_signature = requestParameters['msg_signature']
    timestamp = requestParameters['timestamp']
    nonce = requestParameters['nonce']
    try:
        decrypted_xml = crypto.decrypt_message(
            body,
            msg_signature,
            timestamp,
            nonce
        )
    except (InvalidAppIdException, InvalidSignatureException):
        return
    msg = parse_message(decrypted_xml)
    reply = create_reply("出了點小問(wèn)題,請稍後再試", msg).render()
    print('返回結果--->'+str(reply))  # 用來在騰訊雲控制台打印請求日志
    reply = crypto.encrypt_message(reply, nonce, timestamp)
    return apiReply(reply, txt=True, content_type="application/xml")




def main_handler(event, context):
    body = ''
    httpMethod = event["httpMethod"]
    requestParameters = event['queryString']
    if 'body' in event.keys():
        body = event['body']
    try:
        response = myMain(httpMethod, requestParameters, body=body)
    except:
        response = timeOutReply(httpMethod, requestParameters, body=body)
    return response

請求參數解析和(hé) COS 讀寫部分可(kě)參考上一篇《使用 SCF+COS 快速開發全棧應用》教程

下(xià)面我來捋一下(xià)整個(gè)雲函數的思路(lù)

def main_handler(event, context):
    body = ''
    httpMethod = event["httpMethod"]
    requestParameters = event['queryString']
    if 'body' in event.keys():
        body = event['body']
    try:
        response = myMain(httpMethod, requestParameters, body=body)
    except:
        response = timeOutReply(httpMethod, requestParameters, body=body)
    return response

我們先從main_handler入手。

這裡我們通(tōng)過 API 網關(guān)觸發雲函數在 event 裡拿到了微信公衆号請求的方法、頭部和(hé)請求體,然後傳給 myMain 函數做處理,需要注意的是 myMain 是通(tōng)過timeout_decorator包裝的限時運行函數。

@timeout_decorator.timeout(4, timeout_exception=StopIteration)
def myMain(httpMethod, requestParameters, body=''):
    return wechat(httpMethod, requestParameters, body=body)

當 myMain 函數運行市場超過設定的 4 秒後,就會抛出異常。

然後我們可(kě)以通(tōng)過設置一個(gè) timeOutReply 函數來處理超時後的微信公衆号消息回複,可(kě)是為什麼要這麼做呢(ne)?

可(kě)以看到,當雲函數運行超時後,微信這邊就會顯示「該公衆号提供的服務器(qì)出現故障,請稍後再試」

這對用戶體驗是極不友好的,所以我們需要一個(gè)函數超時後的回複來兜底。

那麼對于一次微信公衆号後台消息請求多長時間算是超時呢(ne)?答案是 5 秒左右,從雲函數後台的調用日志我們可(kě)以得到這個(gè)結果。

不過需要注意的是對于用戶的一次消息請求,微信可(kě)能會每隔 1 秒左右重撥一次請求,直到收到服務器(qì)第一次響應。另外,超過 3 次應該就不會再重撥了,并且在 5 秒超時後即使雲函數調用成功并返回了數據,用戶也不會再接收到消息了~

所以我們就很有必要将自己的雲函數的運行時長限制在 5 秒之内了!

當然隻通(tōng)過配置雲函數超時時長得方式來處理是不正确的,因為這樣做雲函數超時後就被系統停掉了,并不會向微信返回消息。所以從一開始我就導入了 timeout_decorator 庫來限制主函數的運行時長,并用一個(gè)超時後回複函數來兜底。

另外值得一提的是,在我原始的業(yè)務代碼中(zhōng)是有一些爬蟲,這些爬蟲本來我是單線程順序執行的,考慮到超時問(wèn)題,我在微信雲函數版這裡全部改成了多線程運行來壓縮時間,所以如(rú)果你(nǐ)也有一些比較耗時的小任務話,也可(kě)以嘗試通(tōng)過多線程的方式來壓縮雲函數的運行時長。

我們接着向下(xià)看:

def wechat(httpMethod, requestParameters, body=''):
    if httpMethod == 'GET':
        signature = requestParameters['signature']
        timestamp = requestParameters['timestamp']
        nonce = requestParameters['nonce']
        echo_str = requestParameters['echostr']
        try:
            check_signature(WECHAT_TOKEN, signature, timestamp, nonce)
        except InvalidSignatureException:
            echo_str = 'error'
        return apiReply(echo_str, txt=True, content_type="text/plain")
    elif httpMethod == 'POST':
        msg_signature = requestParameters['msg_signature']
        timestamp = requestParameters['timestamp']
        nonce = requestParameters['nonce']
        try:
            decrypted_xml = crypto.decrypt_message(
                body,
                msg_signature,
                timestamp,
                nonce
            )
        except (InvalidAppIdException, InvalidSignatureException):
            return
        msg = parse_message(decrypted_xml)
        if msg.type == 'text':
            reply = replyMessage(msg)
        elif msg.type == 'image':
            reply = create_reply('哈? ???\n好端端的,給我發圖片幹啥~', msg)
        elif msg.type == 'voice':
            reply = create_reply('哈? ???\n好端端的,給我發語音幹啥~', msg)
        else:
            reply = create_reply('哈? ???\n搞不明白你(nǐ)給我發了啥~', msg)
        reply = reply.render()
        print('返回結果--->'+str(reply))  # 用來在騰訊雲控制台打印請求日志
        reply = crypto.encrypt_message(reply, nonce, timestamp)
        return apiReply(reply, txt=True, content_type="application/xml")
    else:
        msg = parse_message(body)
        reply = create_reply("喵嗚 ?'ω'?", msg)
        reply = reply.render()
        print('返回結果--->'+str(reply))  # 用來在騰訊雲控制台打印請求日志
        reply = crypto.encrypt_message(reply, nonce, timestamp)
        return apiReply(reply, txt=True, content_type="application/xml")

這裡的 wechat 函數就是整個(gè)微信消息的解析過程,首先判斷請求方法是 GET 還是 POST,GET 方法隻在第一次綁定微信後台時會用到,這時我們會從微信服務器(qì)推送的請求參數中(zhōng)拿到 signature, timestamp, echostr 和(hé) nonce 參數,

check_signature(WECHAT_TOKEN, signature, timestamp, nonce)

我們隻需根據自己的公衆号 token 和(hé)來生成簽名與微信服務器(qì)傳過來的 signature 對比看是否一緻,若一緻就說明我們的消息加解密驗證是OK的,然後再将 echostr 原樣返回即可(kě)接入微信公衆号後台。

接入好微信公衆号後,如(rú)果有用戶在後台給我們發送消息,這裡雲函數收到的就是 POST 方法,

elif httpMethod == 'POST':
        msg_signature = requestParameters['msg_signature']
        timestamp = requestParameters['timestamp']
        nonce = requestParameters['nonce']
        try:
            decrypted_xml = crypto.decrypt_message(
                body,
                msg_signature,
                timestamp,
                nonce
            )
        except (InvalidAppIdException, InvalidSignatureException):
            return
        msg = parse_message(decrypted_xml)
        if msg.type == 'text':
            reply = replyMessage(msg)
        elif msg.type == 'image':
            reply = create_reply('哈? ???\n好端端的,給我發圖片幹啥~', msg)
        elif msg.type == 'voice':
            reply = create_reply('哈? ???\n好端端的,給我發語音幹啥~', msg)
        else:
            reply = create_reply('哈? ???\n搞不明白你(nǐ)給我發了啥~', msg)
        reply = reply.render()
        print('返回結果--->'+str(reply))  # 用來在騰訊雲控制台打印請求日志
        reply = crypto.encrypt_message(reply, nonce, timestamp)
        return apiReply(reply, txt=True, content_type="application/xml")

然後我們根據前面在微信公衆号後台拿到的 id,token 和(hé) aes 加密 key 來初始化消息加解密實例并解密還原用戶發送的消息

# 微信公衆号對接
wecaht_id = 'xxxxxxxxxxxxxxx'
WECHAT_TOKEN = 'xxxxxxxxxxxxxxxxxxx'
encoding_aes_key = 'xxxxxxxxxxxxxxxxxxxxxx'


crypto = WeChatCrypto(WECHAT_TOKEN, encoding_aes_key, wecaht_id)

接着判斷一下(xià)消息類型,不同類型的消息可(kě)自行處理

        msg = parse_message(decrypted_xml)
        if msg.type == 'text':
            reply = replyMessage(msg)
        elif msg.type == 'image':
            reply = create_reply('哈? ??? 好端端的,給我發圖片幹啥~', msg)
        elif msg.type == 'voice':
            reply = create_reply('哈? ??? 好端端的,給我發語音幹啥~', msg)
        else:
            reply = create_reply('哈? ??? 搞不明白你(nǐ)給我發了啥~', msg)

需要注意的是當一個(gè)用戶新關(guān)注自己的公衆号時,我們收到的是一個(gè)其他類型的消息,也就是上面的最後一個(gè)判斷項,這裡你(nǐ)可(kě)以自己設置新關(guān)注用戶的歡迎語

        reply = create_reply('哈? ???\n搞不明白你(nǐ)給我發了啥~', msg)
        reply = reply.render()
        print('返回結果--->'+str(reply))  # 用來在騰訊雲控制台打印請求日志
        reply = crypto.encrypt_message(reply, nonce, timestamp)
        return apiReply(reply, txt=True, content_type="application/xml")

之後我們通(tōng)過 create_reply 來快速創建一個(gè)文(wén)本回複,并通(tōng)過 render() 來生成 xml 回複消息文(wén)本。因為我之前在後台設置的是安全模式,所以還需要把 xml 重新通(tōng)過 crypto.encrypt_message 方法加密,然後才能把加密後的回複消息返回給微信服務器(qì)。

上一篇文(wén)章我有提到我們不能直接返回消息,需要按照特定的格式返回數據(API 網關(guān)需要開啟響應集成)

# api網關(guān)響應集成
def apiReply(reply, txt=False, content_type='application/json', code=200):
    return {
        "isBase64Encoded": False,
        "statusCode": code,
        "headers": {'Content-Type': content_type},
        "body": json.dumps(reply, ensure_ascii=False) if not txt else str(reply)
    }

04

上線發布雲函數、添加 API 網關(guān)觸發器(qì)、啟用響應集成

參考上一篇教程 《使用 SCF+COS 快速開發全棧應用》

05

修改微信公衆号後台服務器(qì)配置

終于到最後一步了,如(rú)果你(nǐ)已經上線發布了好自己的雲函數,那麼快去微信公衆号後台綁定一下(xià)自己的後台服務器(qì)配置吧~

呼~ 大功告成

點擊閱讀原文(wén),領取 COS 限時1元禮包!

相關(guān)案例查看更多