您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何基于Serverless使用云函數SCF+COS免費運營微信公眾號,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
是的,你沒聽錯,這一次我來帶大家直接上手運營微信公眾號。
操作步驟
在上一篇《萬物皆可Serverless之使用SCF+COS快速開發全棧應用》教程中,
我們用騰訊云無服務器云函數 SCF 和對象存儲實現了一個后端云函數,這個云函數可以根據我們的請求返回對應的結果。
現在我們將嘗試在這個云函數的基礎上解析微信 XML 消息,實現公眾號消息的自動回復,關鍵詞回復,文字菜單等功能。
為了快速完成開發,這里我們選擇 python 第三方開源庫 wechatpy 來接入微信公眾平臺。
wechatpy 支持以下功能
普通公眾平臺被動響應和主動調用 API
企業微信 API
微信支付 API
第三方平臺代公眾號調用接口 API
小程序云開發 API
可見功能是十分完整的,不僅支持普通公眾平臺主被動調用,企業微信和微信支付,甚至還支持第三方平臺代公眾號調用接口,拿來運營微信公眾號是十分綽綽有余的~
由于騰訊云函數的運行環境中缺少第三方庫,需要我們自己手動上傳添加依賴,這里我們需要添加的第三方依賴有:wechatpy
、otionaldict
、xmltodict
以及 timeout\_decorator
其中 wechatpy
需要依賴 otionaldict
、xmltodict
,timeout\_decorator
是用來限制函數運行時長的,具體的依賴文件可以自行 pip 安裝后 copy 到云函數項目根目錄,如上圖。
這里需要記下自己的 AppID、Token 和 EncodingAESKey,消息加密方式建議選為安全模式。這個頁面先不要關,一會兒上線發布好云函數還需要過來再次修改配置。
這一步可以直接參考 wechatpy 的官方文檔
Life is short, show me the code.
這里我就直接上代碼了(原始業務代碼已略去,可以按照自己的需求開發)
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 文件讀寫 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網關響應集成 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() # 獲取回復關鍵詞 if '@' in txt: keys = txt.split('@') if keys[0] == '電影': #do something return if keys[0] == '音樂': #do something return if keys[0] == '下架': #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(): # 如果消息在回復關鍵詞內則自動回復 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搞不明白你給我發了啥~', 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("出了點小問題,請稍后再試", 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
請求參數解析和COS讀寫部分可參考上一篇《萬物皆可 Serverless 之使用 SCF+COS 快速開發全棧應用》教程
下面我來捋一下整個云函數的思路
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
入手,
這里我們通過 API 網關觸發云函數在 event 里拿到了微信公眾號請求的方法、頭部和請求體,然后傳給 myMain 函數做處理,需要注意的是 myMain 是通過 timeout\_decorator
包裝的限時運行函數。
@timeout_decorator.timeout(4, timeout_exception=StopIteration) def myMain(httpMethod, requestParameters, body=''): return wechat(httpMethod, requestParameters, body=body)
當 myMain 函數運行市場超過設定的 4 秒后,就會拋出異常,
然后我們可以通過設置一個 timeOutReply
函數來處理超時后的微信公眾號消息回復,可是為什么要這么做呢?
可以看到,當云函數運行超時后,微信這邊就會顯示「該公眾號提供的服務器出現故障,請稍后再試」
這對用戶體驗是極不友好的,所以我們需要一個函數超時后的回復來兜底。
那么對于一次微信公眾號后臺消息請求多長時間算是超時呢?答案是 5 秒左右,從云函數后臺的調用日志我們可以得到這個結果。
不過需要注意的是對于用戶的一次消息請求,微信可能會每隔 1 秒左右重撥一次請求,直到收到服務器第一次響應。另外,超過 3 次應該就不會再重撥了,并且在 5 秒超時后即使云函數調用成功并返回了數據,用戶也不會再接收到消息了~
所以我們就很有必要將自己的云函數的運行時長限制在 5 秒之內了!
當然只通過配置云函數超時時長得方式來處理是不正確的,因為這樣做云函數超時后就被系統停掉了,并不會向微信返回消息。所以從一開始我就導入了 timeout\_decorator
庫來限制主函數的運行時長,并用一個超時后回復函數來兜底。
另外值得一提的是,在我原始的業務代碼中是有一些爬蟲,這些爬蟲本來我是單線程順序執行的,考慮到超時問題,我在微信云函數版這里全部改成了多線程運行來壓縮時間,所以如果你也有一些比較耗時的小任務話,也可以嘗試通過多線程的方式來壓縮云函數的運行時長。
我們接著向下看:
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搞不明白你給我發了啥~', 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 函數就是整個微信消息的解析過程,首先判斷請求方法是 GET 還是 POST,GET 方法只在第一次綁定微信后臺時會用到,這時我們會從微信服務器推送的請求參數中拿到 signature
, timestamp
, echostr
和 nonce
參數,
check_signature(WECHAT_TOKEN, signature, timestamp, nonce)
我們只需根據自己的公眾號 token 和來生成簽名與微信服務器傳過來的 signature 對比看是否一致,若一致就說明我們的消息加解密驗證是OK的,然后再將 echostr 原樣返回即可接入微信公眾號后臺。
接入好微信公眾號后,如果有用戶在后臺給我們發送消息,這里云函數收到的就是 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搞不明白你給我發了啥~', 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 和 aes 加密 key 來初始化消息加解密實例并解密還原用戶發送的消息
# 微信公眾號對接 wecaht_id = 'xxxxxxxxxxxxxxx' WECHAT_TOKEN = 'xxxxxxxxxxxxxxxxxxx' encoding_aes_key = 'xxxxxxxxxxxxxxxxxxxxxx' crypto = WeChatCrypto(WECHAT_TOKEN, encoding_aes_key, wecaht_id)
接著判斷一下消息類型,不同類型的消息可自行處理
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('哈? ??? 搞不明白你給我發了啥~', msg)
需要注意的是當一個用戶新關注自己的公眾號時,我們收到的是一個其他類型的消息,也就是上面的最后一個判斷項,這里你可以自己設置新關注用戶的歡迎語
reply = create_reply('哈? ???\n搞不明白你給我發了啥~', msg) reply = reply.render() print('返回結果--->'+str(reply)) # 用來在騰訊云控制臺打印請求日志 reply = crypto.encrypt_message(reply, nonce, timestamp) return apiReply(reply, txt=True, content_type="application/xml")
之后我們通過 create\_reply
來快速創建一個文本回復,并通過 render() 來生成 xml 回復消息文本。因為我之前在后臺設置的是安全模式,所以還需要把 xml 重新通過 crypto.encrypt\_message
方法加密,然后才能把加密后的回復消息返回給微信服務器。
上一篇文章我有提到我們不能直接返回消息,需要按照特定的格式返回數據(API 網關需要開啟響應集成)
# api網關響應集成 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) }
參考上一篇教程 《萬物皆可 Serverless 之使用 SCF+COS 快速開發全棧應用》
終于到最后一步了,如果你已經上線發布了好自己的云函數,那么快去微信公眾號后臺綁定一下自己的后臺服務器配置吧~
呼~ 大功告成
關于如何基于Serverless使用云函數SCF+COS免費運營微信公眾號就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。