最近做的企微机器人,光发文字不够——客户咨询产品要发图文卡片带"查看详情"按钮,处理工单要发卡片带"已解决""未解决"按钮让客户点。这篇重点讲怎么用企微接口发交互卡片、怎么接收按钮点击回调、怎么处理回调后的业务逻辑。
底层用的是Eyun 平台开放的企微 API,统一 POST+JSON,鉴权用 App Token 加 appid,响应封套{code, data, detail, message, time},code 为 0 成功。
发图文卡片:sendRichText
图文卡片用消息模块的 sendRichText 接口。卡片有标题、描述、图片、跳转链接,还能带按钮:
import requests BASE = "https://api.eyun.com" HEADERS = {"Authorization": "Bearer eyk_xxxx", "Content-Type": "application/json"} def send_product_card(appid, to_uin, product): card = { "title": product["name"], "description": f"价格:{product['price']}元\n{product['desc'][:50]}", "imageUrl": product["image"], "url": f"https://shop.example.com/product/{product['id']}", "buttons": [ {"text": "查看详情", "url": f"https://shop.example.com/product/{product['id']}"}, {"text": "立即咨询", "action": "consult", "data": {"product_id": product["id"]}} ] } resp = requests.post( f"{BASE}/wx-api/api/message/sendRichText", headers=HEADERS, json={"appid": appid, "to": to_uin, "cards": [card]} ) return resp.json()["code"] == 0buttons里的按钮分两类:跳转链接按钮(url)和回调按钮(action+data)。回调按钮点击后会触发 Webhook 事件。
接收按钮回调:card.action.trigger
客户点回调按钮后,企微通过 Webhook 回调,事件类型是card.action.trigger:
from flask import Flask, request app = Flask(__name__) @app.route("/wx-api/webhook/", methods=["POST"]) def webhook(): event = request.headers.get("X-Eyun-Event") payload = request.json["data"] if event == "card.action.trigger": appid = payload["appid"] from_uin = payload["fromUin"] action = payload["action"] # 按钮的 action 字段 data = payload.get("data", {}) # 按钮的 data 字段 handle_card_action(appid, from_uin, action, data) return "ok"action标识是哪个按钮被点了,data是按钮带的业务数据(如 product_id)。根据 action 走不同业务逻辑。
处理按钮回调:按 action 分发
def handle_card_action(appid, from_uin, action, data): if action == "consult": # 客户点了"立即咨询",建咨询工单 ticket_id = crm_api.create_consult( customer_uin=from_uin, product_id=data["product_id"] ) # 回发消息确认 send_text(appid, from_uin, f"已为您创建咨询工单 {ticket_id},客服稍后联系") elif action == "resolve": # 客户点了"已解决",关单 ticket_id = data["ticket_id"] crm_api.close_ticket(ticket_id) send_text(appid, from_uin, "感谢反馈,工单已关闭") elif action == "unresolved": # 客户点了"未解决",升级 ticket_id = data["ticket_id"] crm_api.escalate_ticket(ticket_id) send_text(appid, from_uin, "已为您升级工单,主管会尽快联系")每个 action 对应一段业务逻辑。回调里的 data 是发卡片时带的,所以能知道是哪个产品、哪个工单。
发工单卡片:带解决/未解决按钮
客服处理完工单后,发卡片让客户确认:
def send_ticket_card(appid, to_uin, ticket_id): card = { "title": f"工单 {ticket_id} 处理完成", "description": "请问问题是否已解决?", "buttons": [ {"text": "已解决", "action": "resolve", "data": {"ticket_id": ticket_id}}, {"text": "未解决", "action": "unresolved", "data": {"ticket_id": ticket_id}} ] } requests.post( f"{BASE}/wx-api/api/message/sendRichText", headers=HEADERS, json={"appid": appid, "to": to_uin, "cards": [card]} )客户点按钮,回调到 handle_card_action,根据 action 关单或升级。闭环完成。
卡片的状态更新
卡片发出去后,业务状态变了(工单关了),卡片要不要更新?企微卡片不支持动态更新,只能重新发一张新卡片,旧卡片还在聊天记录里。处理方式:
新卡片带"此工单已关闭"提示
旧卡片的按钮点了也返回"工单已处理"
不追求旧卡片更新,客户看最新的卡片就行。
按钮回调的幂等
客户可能连点几下按钮,回调会触发多次。回调处理要幂等:
def handle_card_action(appid, from_uin, action, data): # 用 action + data 做幂等键 idem_key = f"card_action:{action}:{data.get('ticket_id', '')}" if not redis.set(idem_key, "1", nx=True, ex=3600): return # 重复回调,跳过 # 处理业务...不做幂等,客户连点"已解决",工单被关多次(虽然关多次没副作用,但其他 action 可能有)。
写在最后
交互消息这套东西,本质是用好企微的卡片接口——sendRichText 发带按钮的卡片、Webhook 收 card.action.trigger 回调、按 action 分发处理业务逻辑。把卡片和回调串起来,机器人不只是发文字,还能和客户交互。