知識(shí)
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們在追求其視覺表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!
您當(dāng)前位置>首頁 » 新聞資訊 » 公眾號(hào)相關(guān) >
使用云函數(shù)SCF+COS免費(fèi)運(yùn)營微信公眾號(hào)
發(fā)表時(shí)間:2020-9-30
發(fā)布人:葵宇科技
瀏覽次數(shù):78
是的,你沒聽錯(cuò),這一次我來帶大家直接上手運(yùn)營微信公眾號(hào)。
震驚,Awesome,哼,我才不信捏,所謂無圖無真相 ~
效果展示
更多的體驗(yàn),可以關(guān)注我的微信公眾號(hào):乂乂又又 (僅供測試,不要亂搞哈~)
嗯,這次我信了,快點(diǎn)教一下我吧,嚶嚶嚶~
操作步驟
在上一篇《使用SCF+COS快速開發(fā)全棧應(yīng)用》教程中,我們用騰訊云無服務(wù)器云函數(shù) SCF 和對(duì)象存儲(chǔ) COS 實(shí)現(xiàn)了一個(gè)后端云函數(shù),這個(gè)云函數(shù)可以根據(jù)我們的請(qǐng)求返回對(duì)應(yīng)的結(jié)果。
現(xiàn)在我們將嘗試在這個(gè)云函數(shù)的基礎(chǔ)上解析微信 XML 消息,實(shí)現(xiàn)公眾號(hào)消息的自動(dòng)回復(fù),關(guān)鍵詞回復(fù),文字菜單等功能。
01
添加相關(guān)依賴
為了快速完成開發(fā),這里我們選擇 python 第三方開源庫 wechatpy 來接入微信公眾平臺(tái)。
wechatpy 支持以下功能:
普通公眾平臺(tái)被動(dòng)響應(yīng)和主動(dòng)調(diào)用 API
企業(yè)微信 API
微信支付 API
第三方平臺(tái)代公眾號(hào)調(diào)用接口 API
小程序云開發(fā) API
可見功能是十分完整的,不僅支持普通公眾平臺(tái)主被動(dòng)調(diào)用,企業(yè)微信和微信支付,甚至還支持第三方平臺(tái)代公眾號(hào)調(diào)用接口,拿來運(yùn)營微信公眾號(hào)是十分綽綽有余的~
由于騰訊云函數(shù)的運(yùn)行環(huán)境中缺少第三方庫,需要我們自己手動(dòng)上傳添加依賴,這里我們需要添加的第三方依賴有:wechatpy
、optionaldict
、xmltodict
以及 timeout_decorator
其中 wechatpy
需要依賴 optionaldict
、xmltodict
,timeout_decorator
是用來限制函數(shù)運(yùn)行時(shí)長的,具體的依賴文件可以自行 pip 安裝后 copy 到云函數(shù)項(xiàng)目根目錄,如上圖。
02
接入微信公眾號(hào)
這里需要記下自己的 AppID、Token 和 EncodingAESKey,消息加密方式建議選為安全模式。這個(gè)頁面先不要關(guān),一會(huì)兒上線發(fā)布好云函數(shù)還需要過來再次修改配置。
03
編寫云函數(shù)解析并回復(fù)微信公眾號(hào)消息
這一步可以直接參考 wechatpy 的官方文檔
地址:http://docs.wechatpy.org/zh_CN/master/quickstart.html#id2
Life is short, show me the code.
這里我就直接上代碼了(原始業(yè)務(wù)代碼已略去,可以按照自己的需求開發(fā))
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
# 騰訊云對(duì)象存儲(chǔ)依賴
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
# 配置存儲(chǔ)桶
appid = '66666666666'
secret_id = u'xxxxxxxxxxxxxxx'
secret_key = u'xxxxxxxxxxxxxxx'
region = u'ap-chongqing'
bucket = 'name'+'-'+appid
# 對(duì)象存儲(chǔ)實(shí)例
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') # 讀取數(shù)據(jù)
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
# 微信公眾號(hào)對(duì)接
wecaht_id = 'xxxxxxxxxxxxxxx'
WECHAT_TOKEN = 'xxxxxxxxxxxxxxxxxxx'
encoding_aes_key = 'xxxxxxxxxxxxxxxxxxxxxx'
crypto = WeChatCrypto(WECHAT_TOKEN, encoding_aes_key, wecaht_id)
# api網(wǎng)關(guān)響應(yīng)集成
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('請(qǐng)求信息--->'+ip+'%'+txt) # 用來在騰訊云控制臺(tái)打印請(qǐng)求日志
replysTxtMap = getReplys() # 獲取回復(fù)關(guān)鍵詞
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] == '回復(fù)': #do something
return
if keys[0] == '刪除': #do something
return
elif txt in replysTxtMap.keys(): # 如果消息在回復(fù)關(guān)鍵詞內(nèi)則自動(dòng)回復(fù)
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好端端的,給我發(fā)圖片干啥~', msg)
elif msg.type == 'voice':
reply = create_reply('哈? ???\n好端端的,給我發(fā)語音干啥~', msg)
else:
reply = create_reply('哈? ???\n搞不明白你給我發(fā)了啥~', msg)
reply = reply.render()
print('返回結(jié)果--->'+str(reply)) # 用來在騰訊云控制臺(tái)打印請(qǐng)求日志
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('返回結(jié)果--->'+str(reply)) # 用來在騰訊云控制臺(tái)打印請(qǐng)求日志
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("出了點(diǎn)小問題,請(qǐng)稍后再試", msg).render()
print('返回結(jié)果--->'+str(reply)) # 用來在騰訊云控制臺(tái)打印請(qǐng)求日志
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
請(qǐng)求參數(shù)解析和 COS 讀寫部分可參考上一篇《使用 SCF+COS 快速開發(fā)全棧應(yīng)用》教程
下面我來捋一下整個(gè)云函數(shù)的思路
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 網(wǎng)關(guān)觸發(fā)云函數(shù)在 event 里拿到了微信公眾號(hào)請(qǐng)求的方法、頭部和請(qǐng)求體,然后傳給 myMain 函數(shù)做處理,需要注意的是 myMain 是通過timeout_decorator
包裝的限時(shí)運(yùn)行函數(shù)。
@timeout_decorator.timeout(4, timeout_exception=StopIteration)
def myMain(httpMethod, requestParameters, body=''):
return wechat(httpMethod, requestParameters, body=body)
當(dāng) myMain 函數(shù)運(yùn)行市場超過設(shè)定的 4 秒后,就會(huì)拋出異常。
然后我們可以通過設(shè)置一個(gè) timeOutReply
函數(shù)來處理超時(shí)后的微信公眾號(hào)消息回復(fù),可是為什么要這么做呢?
可以看到,當(dāng)云函數(shù)運(yùn)行超時(shí)后,微信這邊就會(huì)顯示「該公眾號(hào)提供的服務(wù)器出現(xiàn)故障,請(qǐng)稍后再試」
這對(duì)用戶體驗(yàn)是極不友好的,所以我們需要一個(gè)函數(shù)超時(shí)后的回復(fù)來兜底。
那么對(duì)于一次微信公眾號(hào)后臺(tái)消息請(qǐng)求多長時(shí)間算是超時(shí)呢?答案是 5 秒左右,從云函數(shù)后臺(tái)的調(diào)用日志我們可以得到這個(gè)結(jié)果。
不過需要注意的是對(duì)于用戶的一次消息請(qǐng)求,微信可能會(huì)每隔 1 秒左右重?fù)芤淮握?qǐng)求,直到收到服務(wù)器第一次響應(yīng)。另外,超過 3 次應(yīng)該就不會(huì)再重?fù)芰?#xff0c;并且在 5 秒超時(shí)后即使云函數(shù)調(diào)用成功并返回了數(shù)據(jù),用戶也不會(huì)再接收到消息了~
所以我們就很有必要將自己的云函數(shù)的運(yùn)行時(shí)長限制在 5 秒之內(nèi)了!
當(dāng)然只通過配置云函數(shù)超時(shí)時(shí)長得方式來處理是不正確的,因?yàn)檫@樣做云函數(shù)超時(shí)后就被系統(tǒng)停掉了,并不會(huì)向微信返回消息。所以從一開始我就導(dǎo)入了 timeout_decorator
庫來限制主函數(shù)的運(yùn)行時(shí)長,并用一個(gè)超時(shí)后回復(fù)函數(shù)來兜底。
另外值得一提的是,在我原始的業(yè)務(wù)代碼中是有一些爬蟲,這些爬蟲本來我是單線程順序執(zhí)行的,考慮到超時(shí)問題,我在微信云函數(shù)版這里全部改成了多線程運(yùn)行來壓縮時(shí)間,所以如果你也有一些比較耗時(shí)的小任務(wù)話,也可以嘗試通過多線程的方式來壓縮云函數(shù)的運(yùn)行時(shí)長。
我們接著向下看:
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好端端的,給我發(fā)圖片干啥~', msg)
elif msg.type == 'voice':
reply = create_reply('哈? ???\n好端端的,給我發(fā)語音干啥~', msg)
else:
reply = create_reply('哈? ???\n搞不明白你給我發(fā)了啥~', msg)
reply = reply.render()
print('返回結(jié)果--->'+str(reply)) # 用來在騰訊云控制臺(tái)打印請(qǐng)求日志
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('返回結(jié)果--->'+str(reply)) # 用來在騰訊云控制臺(tái)打印請(qǐng)求日志
reply = crypto.encrypt_message(reply, nonce, timestamp)
return apiReply(reply, txt=True, content_type="application/xml")
這里的 wechat 函數(shù)就是整個(gè)微信消息的解析過程,首先判斷請(qǐng)求方法是 GET 還是 POST,GET 方法只在第一次綁定微信后臺(tái)時(shí)會(huì)用到,這時(shí)我們會(huì)從微信服務(wù)器推送的請(qǐng)求參數(shù)中拿到 signature
, timestamp
, echostr
和 nonce
參數(shù),
check_signature(WECHAT_TOKEN, signature, timestamp, nonce)
我們只需根據(jù)自己的公眾號(hào) token 和來生成簽名與微信服務(wù)器傳過來的 signature 對(duì)比看是否一致,若一致就說明我們的消息加解密驗(yàn)證是OK的,然后再將 echostr 原樣返回即可接入微信公眾號(hào)后臺(tái)。
接入好微信公眾號(hào)后,如果有用戶在后臺(tái)給我們發(fā)送消息,這里云函數(shù)收到的就是 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好端端的,給我發(fā)圖片干啥~', msg)
elif msg.type == 'voice':
reply = create_reply('哈? ???\n好端端的,給我發(fā)語音干啥~', msg)
else:
reply = create_reply('哈? ???\n搞不明白你給我發(fā)了啥~', msg)
reply = reply.render()
print('返回結(jié)果--->'+str(reply)) # 用來在騰訊云控制臺(tái)打印請(qǐng)求日志
reply = crypto.encrypt_message(reply, nonce, timestamp)
return apiReply(reply, txt=True, content_type="application/xml")
然后我們根據(jù)前面在微信公眾號(hào)后臺(tái)拿到的 id,token 和 aes 加密 key 來初始化消息加解密實(shí)例并解密還原用戶發(fā)送的消息
# 微信公眾號(hào)對(duì)接
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('哈? ??? 好端端的,給我發(fā)圖片干啥~', msg)
elif msg.type == 'voice':
reply = create_reply('哈? ??? 好端端的,給我發(fā)語音干啥~', msg)
else:
reply = create_reply('哈? ??? 搞不明白你給我發(fā)了啥~', msg)
需要注意的是當(dāng)一個(gè)用戶新關(guān)注自己的公眾號(hào)時(shí),我們收到的是一個(gè)其他類型的消息,也就是上面的最后一個(gè)判斷項(xiàng),這里你可以自己設(shè)置新關(guān)注用戶的歡迎語
reply = create_reply('哈? ???\n搞不明白你給我發(fā)了啥~', msg)
reply = reply.render()
print('返回結(jié)果--->'+str(reply)) # 用來在騰訊云控制臺(tái)打印請(qǐng)求日志
reply = crypto.encrypt_message(reply, nonce, timestamp)
return apiReply(reply, txt=True, content_type="application/xml")
之后我們通過 create_reply
來快速創(chuàng)建一個(gè)文本回復(fù),并通過 render() 來生成 xml 回復(fù)消息文本。因?yàn)槲抑霸诤笈_(tái)設(shè)置的是安全模式,所以還需要把 xml 重新通過 crypto.encrypt_message
方法加密,然后才能把加密后的回復(fù)消息返回給微信服務(wù)器。
上一篇文章我有提到我們不能直接返回消息,需要按照特定的格式返回?cái)?shù)據(jù)(API 網(wǎng)關(guān)需要開啟響應(yīng)集成)
# api網(wǎng)關(guān)響應(yīng)集成
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
上線發(fā)布云函數(shù)、添加 API 網(wǎng)關(guān)觸發(fā)器、啟用響應(yīng)集成
參考上一篇教程 《使用 SCF+COS 快速開發(fā)全棧應(yīng)用》
05
修改微信公眾號(hào)后臺(tái)服務(wù)器配置
終于到最后一步了,如果你已經(jīng)上線發(fā)布了好自己的云函數(shù),那么快去微信公眾號(hào)后臺(tái)綁定一下自己的后臺(tái)服務(wù)器配置吧~
呼~ 大功告成
點(diǎn)擊閱讀原文,領(lǐng)取 COS 限時(shí)1元禮包!
相關(guān)案例查看更多
相關(guān)閱讀
- 霸屏推廣
- 云南網(wǎng)站建設(shè)服務(wù)
- 云南做網(wǎng)站
- 昆明小程序設(shè)計(jì)
- 昆明網(wǎng)站開發(fā)
- 開通微信小程序被騙
- 云南小程序開發(fā)公司推薦
- 云南軟件定制
- 昆明網(wǎng)絡(luò)公司
- 汽車報(bào)廢回收管理軟件
- painter
- 云南網(wǎng)站建設(shè)哪家公司好
- 云南網(wǎng)站建設(shè)列表網(wǎng)
- 軟件開發(fā)
- 云南網(wǎng)站建設(shè)專業(yè)品牌
- 云南小程序商城
- 網(wǎng)站建設(shè)方案 doc
- 云南省建設(shè)廳網(wǎng)站官網(wǎng)
- 安家微信小程序
- 人口普查小程序
- vue開發(fā)小程序
- 重慶網(wǎng)站建設(shè)公司
- 商標(biāo)注冊
- 云南網(wǎng)站建設(shè)百度官方
- 保險(xiǎn)網(wǎng)站建設(shè)公司
- 汽車報(bào)廢軟件
- 網(wǎng)站建設(shè)電話
- 排名
- 云南小程序開發(fā)課程
- 全國前十名小程序開發(fā)公司