企業號 (現企業微信) 通過 **應用發送消息 API** 向成員發送各類消息,需獲取 `access_token` (企業級,非公眾號)。
public class WechatWorkClient {
private static final String SEND_URL =
"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s";
private final String accessToken;
private final HttpClient httpClient;
public WechatWorkClient(String accessToken) {
this.accessToken = accessToken;
this.httpClient = HttpClientBuilder.create().build();
}
public JsonObject send(String jsonBody) throws IOException {
HttpPost post = new HttpPost(String.format(SEND_URL, accessToken));
post.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
try (CloseableHttpResponse resp = httpClient.execute(post)) {
String result = EntityUtils.toString(resp.getEntity(), "UTF-8");
return JsonParser.parseString(result).getAsJsonObject();
}
}
}
{
"touser": "UserID1|UserID2", // 接收者,多個用 | 分隔,@all 發送給全員
"toparty": "PartyID1|PartyID2", // 接收部門
"totag": "TagID1|TagID2", // 接收標籤
"msgtype": "text",
"agentid": 1000001, // 應用 ID
"text": {
"content": "您好,這是一條測試文本消息。"
},
"safe": 0, // 0-不加密 1-加密
"enable_id_trans": 0, // 是否轉換 userID 為 openID
"enable_duplicate_check": 0, // 是否重複檢查
"duplicate_check_interval": 1800
}
public JsonObject sendText(String touser, String content, int agentId) {
JsonObject msg = new JsonObject();
msg.addProperty("touser", touser);
msg.addProperty("msgtype", "text");
msg.addProperty("agentid", agentId);
JsonObject text = new JsonObject();
text.addProperty("content", content);
msg.add("text", text);
return send(msg.toString());
}
POST https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=TOKEN&type=image
Content-Type: multipart/form-data
// 響應
{"errcode":0,"errmsg":"ok","type":"image","media_id":"3J6Y...","created_at":1234567890}
public String uploadImage(File imageFile) throws IOException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token="
+ accessToken + "&type=image";
HttpPost post = new HttpPost(url);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addBinaryBody("media", imageFile, ContentType.IMAGE_JPEG, imageFile.getName());
post.setEntity(builder.build());
try (CloseableHttpResponse resp = httpClient.execute(post)) {
JsonObject obj = JsonParser.parseString(
EntityUtils.toString(resp.getEntity())).getAsJsonObject();
return obj.get("media_id").getAsString();
}
}
public JsonObject sendImage(String touser, String mediaId, int agentId) {
JsonObject msg = new JsonObject();
msg.addProperty("touser", touser);
msg.addProperty("msgtype", "image");
msg.addProperty("agentid", agentId);
JsonObject image = new JsonObject();
image.addProperty("media_id", mediaId);
msg.add("image", image);
return send(msg.toString());
}
public JsonObject sendVoice(String touser, String mediaId, int agentId) {
JsonObject msg = new JsonObject();
msg.addProperty("touser", touser);
msg.addProperty("msgtype", "voice");
msg.addProperty("agentid", agentId);
JsonObject voice = new JsonObject();
voice.addProperty("media_id", mediaId);
msg.add("voice", voice);
return send(msg.toString());
}
{
"msgtype": "news",
"news": {
"articles": [
{
"title": "標題",
"description": "描述",
"url": "https://example.com/detail",
"picurl": "https://example.com/cover.jpg"
}
]
}
}
public JsonObject sendNews(String touser, Listarticles, int agentId) { JsonObject msg = new JsonObject(); msg.addProperty("touser", touser); msg.addProperty("msgtype", "news"); msg.addProperty("agentid", agentId); JsonObject news = new JsonObject(); JsonArray arr = new JsonArray(); for (Article a : articles) { JsonObject item = new JsonObject(); item.addProperty("title", a.getTitle()); item.addProperty("description", a.getDesc()); item.addProperty("url", a.getUrl()); item.addProperty("picurl", a.getPicUrl()); arr.add(item); } news.add("articles", arr); msg.add("news", news); return send(msg.toString()); } static class Article { String title, desc, url, picUrl; // getter/setter }
| errcode | 含義 | 處理建議 |
|---|---|---|
| 0 | 成功 | - |
| 40037 | 模板不存在/參數錯誤 | 檢查 JSON 結構、必填字段 |
| 41028 | media_id 失效/不存在 | 重新上傳素材,media_id 3天有效 |
| 41029 | media_id 類型不匹配 | 圖片用 image,語音用 voice,勿混用 |
| 45009 | 接口調用超限 | 檢查應用頻率限制,實施佇列限流 |
| 60020 | 接收者不存在 | 驗證 userid/partyid/tagid 有效性 |
| 81011 | 內容含敏感詞 | 內容審核、替換敏感詞 |
@Service
public class WechatWorkNotifyService {
@Autowired WechatWorkClient client;
public void notifyText(String user, String content) {
try {
JsonObject result = client.sendText(user, content, 1000001);
if (result.get("errcode").getAsInt() != 0) {
log.error("發送失敗: {}", result.get("errmsg"));
}
} catch (Exception e) {
log.error("發送異常", e);
}
}
public void notifyWithImage(String user, String text, File image) {
try {
String mediaId = client.uploadImage(image);
client.sendImage(user, mediaId, 1000001);
} catch (Exception e) { log.error("圖片通知失敗", e); }
}
}