企业内部的运维告警、审批提醒、订单异常通知,如果只靠邮件推送,时效性往往跟不上。企业微信作为很多公司的内部沟通工具,其开放的 API 可以让业务系统直接把消息推送到员工手机上,几乎零延迟。本文将介绍如何在 Spring Boot 项目中整合企业微信 API,从最简单的群机器人 Webhook,到需要认证的应用消息接口,逐步实现一套可复用的内部通知服务。

一、方案选型:群机器人还是自建应用
企业微信提供了两种主流的消息推送方式,理解它们的区别是做技术选型的第一步。
第一种是群机器人 Webhook。在企业微信群里添加机器人后,会得到一个 Webhook 地址,任何人拿到这个地址都可以直接 POST JSON 数据发消息,不需要 access_token,不需要回调配置。这种方式实现成本最低,适合往固定的运维群、告警群推送通知。缺点是无法精确指定某个人,只能发到群内,且消息格式有一定限制。
第二种是自建应用消息接口。在企业微信管理后台创建一个自建应用后,通过 corpid、corpsecret 换取 access_token,再调用消息接口,可以精确推送给某个成员、某个部门或者全体人员,还支持文本、卡片、Markdown、小程序通知等多种消息类型。这种方式适合做真正的系统级通知,比如审批待办、账单提醒等场景。
实际项目中两者常常组合使用:告警类广播走机器人,点对点通知走自建应用。下面的实现会把两者统一封装成一个 WecomNotifyService,业务方调用时无需关心底层用的是哪种通道。
二、群机器人 Webhook 推送实现
机器人推送本质上就是一个 HTTP POST 请求,用 Spring 自带的 RestTemplate 或者 WebClient 都可以完成。建议把 Webhook 地址配置在 application.yml 中,方便区分测试群和生产群。
先看配置文件:
wecom:
robot:
webhook: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx-xxxx-xxxx
然后用 @ConfigurationProperties 接收配置,并封装发送方法。这里以文本消息和 Markdown 消息为例:
@Component
@ConfigurationProperties(prefix = "wecom.robot")
public class WecomRobotClient {
private String webhook;
public void setWebhook(String webhook) {
this.webhook = webhook;
}
private final RestTemplate restTemplate = new RestTemplate();
/**
* 发送文本消息,可@指定成员
*/
public void sendText(String content, List<String> mentionedList) {
Map<String, Object> body = new HashMap<>();
body.put("msgtype", "text");
Map<String, Object> text = new HashMap<>();
text.put("content", content);
if (mentionedList != null && !mentionedList.isEmpty()) {
text.put("mentioned_list", mentionedList);
}
body.put("text", text);
post(body);
}
/**
* 发送Markdown消息
*/
public void sendMarkdown(String markdown) {
Map<String, Object> body = new HashMap<>();
body.put("msgtype", "markdown");
Map<String, Object> md = new HashMap<>();
md.put("content", markdown);
body.put("markdown", md);
post(body);
}
private void post(Map<String, Object> body) {
Map<String, Object> resp = restTemplate.postForObject(webhook, body, Map.class);
System.out.println("机器人响应: " + resp);
}
}
有几个细节值得注意。机器人接口有频率限制,每个机器人每分钟最多发 20 条消息,超频会返回错误码 45009,所以告警风暴场景下要做本地限流或者合并告警内容。另外 Markdown 消息支持的语法子集很有限,标题、加粗、链接、引用可用,表格和图片是不可用的,写模板时不要照搬标准 Markdown。
三、自建应用消息:access_token 获取与缓存
自建应用接口的第一步是用 corpid 和 corpsecret 换取 access_token,这个 token 有效期是 7200 秒。企业微信官方明确要求应用需要缓存并定期刷新 token,而不是每次发消息都去请求一次,否则频繁获取会触发限流。
单机场景下可以用一个带过期时间的内存缓存,集群部署时建议放到 Redis 里,用分布式锁避免多个实例同时刷新。下面是一个基于内存的简洁实现,思路同样适用于 Redis:
@Component
public class WecomTokenManager {
@Value("${wecom.corp.id}")
private String corpId;
@Value("${wecom.corp.secret}")
private String corpSecret;
private final RestTemplate restTemplate = new RestTemplate();
// 缓存的token和过期时间
private volatile String accessToken;
private volatile long expireAt = 0;
public synchronized String getToken() {
// 提前5分钟过期,避免边界问题
if (accessToken == null || System.currentTimeMillis() > expireAt - 300_000) {
refresh();
}
return accessToken;
}
private void refresh() {
String url = String.format(
"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s",
corpId, corpSecret);
Map<String, Object> resp = restTemplate.getForObject(url, Map.class);
if (resp != null && Integer.valueOf(0).equals(resp.get("errcode"))) {
accessToken = (String) resp.get("access_token");
expireAt = System.currentTimeMillis()
+ ((Integer) resp.get("expires_in")) * 1000L;
} else {
throw new IllegalStateException("获取access_token失败: " + resp);
}
}
}
这里把过期判断设计成提前五分钟刷新,是因为请求 token 到实际使用之间有时间差,如果卡着 7200 秒刷新,可能在最后几秒用到已失效的 token。corpsecret 属于敏感信息,不要硬编码在代码里,建议通过环境变量或配置中心注入,并且不要提交到代码仓库。
四、应用消息发送与统一通知服务封装
拿到 token 之后,发送应用消息就是调用 message/send 接口,把接收人的 userid、消息类型和内容组装成 JSON 提交即可:
@Component
public class WecomAppClient {
private final WecomTokenManager tokenManager;
private final RestTemplate restTemplate = new RestTemplate();
public WecomAppClient(WecomTokenManager tokenManager) {
this.tokenManager = tokenManager;
}
public void sendTextToUser(String userId, String content) {
String token = tokenManager.getToken();
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + token;
Map<String, Object> body = new HashMap<>();
body.put("touser", userId);
body.put("msgtype", "text");
body.put("agentid", 1000002); // 应用agentid,从管理后台查看
Map<String, Object> text = new HashMap<>();
text.put("content", content);
body.put("text", text);
Map<String, Object> resp = restTemplate.postForObject(url, body, Map.class);
System.out.println("应用消息响应: " + resp);
}
}
最后一步是把机器人通道和应用通道收口到一个门面服务里,业务代码只依赖这个门面,后续更换通知渠道时不必改动业务逻辑:
@Service
public class WecomNotifyService {
private final WecomRobotClient robotClient;
private final WecomAppClient appClient;
public WecomNotifyService(WecomRobotClient robotClient, WecomAppClient appClient) {
this.robotClient = robotClient;
this.appClient = appClient;
}
/**
* 广播告警:发到运维群
*/
public void broadcastAlert(String title, String detail) {
String md = "> **【系统告警】" + title + "**\n> " + detail
+ "\n> 时间: " + java.time.LocalDateTime.now();
robotClient.sendMarkdown(md);
}
/**
* 个人通知:推送给指定员工
*/
public void notifyUser(String userId, String content) {
appClient.sendTextToUser(userId, content);
}
}
在调用层面再做两件事可以让服务更稳:一是针对 errcode 为 40014(token 失效)和 42001(token 过期)的情况做一次强制刷新重试;二是给通知调用加上异步执行(比如丢给 @Async 线程池),避免企业微信接口抖动阻塞主业务线程。通知本质上属于旁路逻辑,失败了只记日志告警,不应该影响业务事务回滚。
总结一下,机器人 Webhook 适合快速打通群通知,自建应用适合精准触达个人,两者封装成统一服务后,一套代码就能覆盖企业内部绝大部分通知场景。生产环境上线前,记得把限流、token 缓存和失败重试这三块补齐,通知链路基本就不会出问题了。
Spring Boot企业微信API系统通知推送修改时间:2026-09-04 04:00:42