官方文档:平台介绍 - QiWe API|企微 API 开发文档
一、业务痛点与技术背景
SCRM 日常:文本接待、发报价单 PDF、发产品图、群公告。高频误区:
把图片 URL 塞进
/msg/sendText→ 对端只看到字符串未区分
userId/roomId→ 发错会话大文件同步上传 → 网关超时
无
client_msg_id→ 客服重试导致双发
正确模型:文本直发;图片/文件 = 上传素材拿 ID → 对应 send method。
二、核心架构设计与数据流转
业务发送意图 │ ▼ Message Facade │ kind=text ──────────────► method=/msg/sendText │ kind=image ──► upload ──► method=/msg/sendImage (imageId) │ kind=file ──► upload ──► method=/msg/sendFile (fileId) ▼ Delivery Store (client_msg_id, guid, toId, chatType, status)统一入口永远是doApi,只改method与params。
三、关键代码与配置示例
3.1 文本发送(带会话类型)
from enum import Enum class ChatType(str, Enum): USER = "user" ROOM = "room" def send_text(client: QiWeClient, guid: str, to_id: str, chat_type: ChatType, content: str, client_msg_id: str): assert chat_type in (ChatType.USER, ChatType.ROOM) # 落库防重 if not delivery.begin(client_msg_id): return {"code": 0, "msg": "duplicate"} body = client.call("/msg/sendText", { "guid": guid, "toId": to_id, "content": content, # 部分环境支持 isNoNeedRead 等字段,以文档为准 }) delivery.success(client_msg_id, body) return body3.2 图片 / 文件两段式
def send_image(client, guid, to_id, path: str, client_msg_id: str): # 1) 上传素材 —— method 名称以文档「素材模块」为准 with open(path, "rb") as f: # 若平台提供独立上传 URL,走 multipart;此处示意 up = client.call("/media/uploadImage", { "guid": guid, # 或先拿上传凭证再 PUT,按文档实现 "fileName": os.path.basename(path), }) image_id = up["data"]["imageId"] # 2) 发送 return client.call("/msg/sendImage", { "guid": guid, "toId": to_id, "imageId": image_id, }) def send_file(client, guid, to_id, path: str): up = client.call("/media/uploadFile", {"guid": guid, "fileName": os.path.basename(path)}) file_id = up["data"]["fileId"] return client.call("/msg/sendFile", { "guid": guid, "toId": to_id, "fileId": file_id, })3.3 TypeScript Facade
export async function send(input: { guid: string; toId: string; chatType: "user" | "room"; kind: "text" | "image" | "file"; text?: string; filePath?: string; clientMsgId: string; }) { switch (input.kind) { case "text": return qiwe.call("/msg/sendText", { guid: input.guid, toId: input.toId, content: input.text, }); case "image": { const { imageId } = await uploadImage(input.guid, input.filePath!); return qiwe.call("/msg/sendImage", { guid: input.guid, toId: input.toId, imageId, }); } case "file": { const { fileId } = await uploadFile(input.guid, input.filePath!); return qiwe.call("/msg/sendFile", { guid: input.guid, toId: input.toId, fileId, }); } } }3.4 编码与 Windows 计划任务陷阱
□ 全链路 UTF-8(请求 JSON、日志、模板文件) □ Windows 任务计划默认代码页可能导致「通道成功、手机乱码」 □ timeout 显式化;查询可重试,发送重试必须幂等键四、生产环境避坑与安全风控
文本接口绝不传二进制;附件必须走素材 ID。
imageId/fileId来自上传返回,禁止手写本地路径当 ID。先打通 sendText,再联调附件,避免登录态问题与素材问题纠缠。
私聊与客户群都要验收,roomId 权限不足时文本通、文件不一定通。
敏感文件:合同/身份证上传前脱敏与鉴权。
接口字段以文首官方文档为准。
五、本篇交付清单
ChatType 显式化文本发送
图片/文件两段式
Facade 统一出口
编码与幂等注意点