企业微信二次开发机器人:文件上传、消息发送与回调确认完整链路

昨晚在更新星云API www.xingyapi.com的底层实战开发笔记时,有个做供应链 SaaS 的全栈老哥找我大吐苦水,说他们系统惹出大祸了。

他们上周搞了个"自动化对账单派发"功能:客户在群里发一句"查账",系统自动生成一份 10MB 的 PDF 对账单发回群里,并等待客户回复"确认"。为了赶进度,这老哥直接在 Webhook 接收端写了个极其暴力的同步方法:收到消息 -> 查库生成 PDF -> 调企微接口上传文件 -> 拿到 media_id 发送消息 -> 更新数据库状态。

结果到了月底结账高峰期,几百个群同时喊"查账"。生成并上传 10MB 的 PDF 耗时将近 8 秒,企微网关只等 5 秒,没收到响应立刻发起夺命连环重试。重试了 3 次,这老哥的服务器因为把几百个 10MB 的 PDF 全读进了 JVM 内存,当场 OOM 宕机。更惨的是,等服务重启后,客户群里连续收到了 3 份一模一样的对账单,客户一头雾水,销售总监直接在钉钉上骂街。

很多兄弟做企微机器人,以为"一问一答"就是调个接口的事。但在涉及大文件 IO 和状态流转的真实业务里,把耗时操作和 Webhook 主线程绑死,就是在给服务器埋雷。 今天咱们直接手撕一套"异步透传上传 + 发送落库 + 回调状态机"的工业级闭环管线。

第一关:斩断同步,构建流式上传管线(绝不爆内存)

遇到"上传素材并发送"这种重 IO 动作,第一铁律是:立刻让 Webhook 极速返回 success,把脏活累活扔给 MQ 异步处理!

当异步 Worker 拿到任务,准备将中台的 PDF 传给企微时,如果你去翻底层的 开发文档,你会看到临时素材上传接口要求的是 multipart/form-data。很多新手在这里会犯致命错误:把文件全量读成 byte[]。

实战防 OOM 流式打法: 你的服务器只做"水管",不做"水桶"。直接把自建 OSS 的输入流,怼到 HTTP 客户端的输出流上。

Java

复制代码
public String uploadMediaToWeCom(String ossFileUrl, String accessToken) {
    String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=" + accessToken + "&type=file";
    
    // 1. 打开中台文件的输入流(绝不全量读入内存!)
    InputStream inStream = new URL(ossFileUrl).openStream();
    
    // 2. 利用 OkHttp 构建流式上传体,内存中只维持几 KB 的 Buffer
    RequestBody streamBody = new RequestBody() {
        @Override
        public MediaType contentType() { return MediaType.parse("application/pdf"); }
        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            sink.writeAll(Okio.source(inStream)); // 管道直连
        }
    };
    
    MultipartBody multipartBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("media", "对账单.pdf", streamBody)
        .build();

    // 3. 执行请求,榨取 media_id
    String response = okHttpClient.newCall(new Request.Builder().url(url).post(multipartBody).build()).execute().body().string();
    return JSONObject.parseObject(response).getString("media_id");
}

第二关:发送并捕获"黄金线索" MsgId

拿着 media_id 调接口把文件发到群里,这只算完成了动作的一半。你怎么知道客户到底确认了没有?

企微的"发送应用消息"接口,在成功执行后,并不是只回你一个 {errcode:0},它还会返回一个极其关键的字段:msgid。这个 msgid 就是贯穿整个状态机的黄金线索!

Java

复制代码
// 异步 Worker 中的发送与落库逻辑
public void sendInvoiceAndRecord(String chatId, String mediaId, String orderNo) {
    // 1. 组装发消息的 JSON
    JSONObject sendJson = new JSONObject();
    sendJson.put("chatid", chatId);
    sendJson.put("msgtype", "file");
    sendJson.put("file", new JSONObject().fluentPut("media_id", mediaId));
    
    // 2. 发起调用
    String respStr = wecomClient.sendMsg(sendJson);
    String msgId = JSONObject.parseObject(respStr).getString("msgid");
    
    // 3. 核心:建立业务与消息的映射记录,等待闭环!
    // 往 t_message_confirm_log 表写一条记录,状态为 WAIT_CONFIRM
    jdbcTemplate.update(
        "INSERT INTO t_message_confirm_log (msg_id, chat_id, order_no, status) VALUES (?, ?, ?, 'WAIT_CONFIRM')",
        msgId, chatId, orderNo
    );
}

第三关:Webhook 回调闭环,终结状态机

当文件发进群里,客户下载看完后,在群里回了一句"确认无误"。这个时候,你的 Webhook 会再次收到一条文本消息的回调。

我们怎么把这句"确认无误",和刚才发出去的那个 PDF 对账单关联起来?

这就需要我们的分发引擎去查刚才留下的"案底":

Java

复制代码
// Webhook 文本消息处理器
public void handleCustomerReply(StandardMsgDTO msg) {
    String chatId = msg.getChatId();
    String content = msg.getTextContent();
    
    if (content.contains("确认")) {
        // 去查这个群里,有没有还在等待确认的订单记录
        List<ConfirmLog> pendingLogs = logRepository.findPendingByChatId(chatId);
        
        if (!pendingLogs.isEmpty()) {
            ConfirmLog log = pendingLogs.get(0);
            
            // 将状态机流转为"已确认",并通知下游财务系统打款!
            logRepository.updateStatus(log.getMsgId(), "CONFIRMED");
            financeService.processOrder(log.getOrderNo());
            
            // 机器人给个反馈
            wecomClient.sendText(chatId, "✅ 收到确认,财务已开始打款流程。");
        }
    }
}

通过 media_id 分离大文件 IO,通过 msgid 记录下发状态,再通过 Webhook 接收客户反馈去扭转状态机。这套逻辑打通,你的机器人就不再是个无脑的"传话筒",而是拥有了完整业务流程接管能力的 SaaS 引擎。

联调刺客:用工具编排"异步拉锯战"压测

这种长链路、跨线程的异步状态机,绝不能凭想象写完就上生产。你必须模拟出网关重试、耗时拉长等极端恶劣的公网环境。

上线前,掏出咱们搞 API 压测必备的 Apifox 或者 Apipost:

  1. 构建双重 Mock 场景:在测试工具里建两个请求。请求 A 模拟客户发送"查账"触发 Webhook;请求 B 模拟客户回复"确认"。

  2. 极限并发重试轰炸:将请求 A 设置为 10 并发,在一秒内全部打向你的本地接口(模拟企微网关由于超时引发的疯狂重试)。

  3. 精准盯盘:

    • 盯你的控制台,看是不是只有第 1 个请求成功触发了异步流式上传,剩下的 9 个被防重机制拦截吞掉了?

    • 盯你的数据库 t_message_confirm_log,是不是只有 1 条干净的 WAIT_CONFIRM 记录,而不是 10 条?

    • 紧接着手动发出请求 B,看数据库的状态是不是瞬间丝滑地扭转为了 CONFIRMED?

别被企微那堆繁杂的回调结构乱了阵脚,把耗时的 IO 彻底丢进异步管道,死死咬住唯一的标识 ID 做状态比对,这才是高级 API 实战开发者该有的工程底盘。

相关推荐
BSD_CGQ10 小时前
人形机器人触觉传感器为什么集中选 FSR 柔性薄膜方案?选型边界与国产替代现状
人工智能·机器人·压力传感器·柔性薄膜压力传感器
XB1775001068312 小时前
3500/40M 176449-01 本特利前卡供应
机器人·自动化·制造
别动我齐刘海1 天前
Linux 系统调试学习1
linux·运维·服务器·c++·学习·机器学习·机器人
木木学AI1 天前
电话机器人被打断后还能接住问题吗?实时语音交互的四项测试
机器人
鲁邦通物联网1 天前
服务机器人如何产品化机器人梯控?软硬件一体化方案设计解析
机器人·巡检机器人·机器人梯控·agv梯控·非侵入式采集·机器人乘梯·机器人自主乘梯
JingliAlex1 天前
企业微信自动回复的话术怎么过审?三级审核流程的搭法
大数据·html·企业微信
workflower1 天前
手术机器人市场发展阶段
大数据·人工智能·机器人·云计算·无人机
TMT星球1 天前
地瓜机器人2026工博会首秀:端云一体全栈AI底座,覆盖视觉检测、工业数采与具身作业
人工智能·机器人·视觉检测
猫先生Mr.Mao1 天前
具身智能之Open X-Embodiment详解:22种机器人数据如何训练出RT-X的跨本体迁移
机器人·论文解读·rt-x·具身智能·vla·open x-embodiment
zelonggao11 天前
智能制造百人会协办上海国际具身智能产业博览会(CIEI 2026)、2026具身智能产业生态大会、第九届中国机器人峰会
机器人·制造