Files
wxSend/src/main/java/cn/van333/wxsend/util/QywxWebhookUtil.java
2025-08-30 23:55:11 +08:00

184 lines
7.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cn.van333.wxsend.util;
import cn.van333.wxsend.business.service.LogService;
import cn.van333.wxsend.util.response.SendRespones;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.hutool.http.HttpRequest.post;
/**
* 企业微信群机器人 Webhook 工具
* 参考文档https://developer.work.weixin.qq.com/document/path/99110
*/
public class QywxWebhookUtil {
private static final Logger logger = LoggerFactory.getLogger(LogService.class);
private static final String QYWX_ORIGIN = "https://qyapi.weixin.qq.com";
private static final String WEBHOOK_SEND = QYWX_ORIGIN + "/cgi-bin/webhook/send";
public static String buildUrl(String key) {
return WEBHOOK_SEND + "?key=" + key;
}
public static String buildSignedUrl(String key, String secret) {
if (secret == null || secret.isEmpty()) {
return buildUrl(key);
}
long timestamp = System.currentTimeMillis() / 1000;
String stringToSign = timestamp + "\n" + secret;
String sign = hmacSha256Base64(secret, stringToSign);
String encodedSign = URLEncoder.encode(sign, StandardCharsets.UTF_8);
return buildUrl(key) + "&timestamp=" + timestamp + "&sign=" + encodedSign;
}
public static SendRespones sendText(String key, String secret, String content,
List<String> mentionedList, List<String> mentionedMobileList) {
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);
}
if (mentionedMobileList != null && !mentionedMobileList.isEmpty()) {
text.put("mentioned_mobile_list", mentionedMobileList);
}
body.put("text", text);
return doSend(buildSignedUrl(key, secret), body);
}
public static SendRespones sendMarkdown(String key, String secret, String content) {
Map<String, Object> body = new HashMap<>();
body.put("msgtype", "markdown");
Map<String, Object> markdown = new HashMap<>();
markdown.put("content", content);
body.put("markdown", markdown);
return doSend(buildSignedUrl(key, secret), body);
}
public static SendRespones sendImageByBase64(String key, String secret, String base64, String md5) {
Map<String, Object> body = new HashMap<>();
body.put("msgtype", "image");
Map<String, Object> image = new HashMap<>();
image.put("base64", base64);
image.put("md5", md5);
body.put("image", image);
return doSend(buildSignedUrl(key, secret), body);
}
public static SendRespones sendImageByUrl(String key, String secret, String imageUrl) {
try {
HttpResponse resp = HttpRequest.get(imageUrl).execute();
byte[] bytes = resp.bodyBytes();
String base64 = java.util.Base64.getEncoder().encodeToString(bytes);
String md5 = DigestUtil.md5Hex(bytes);
return sendImageByBase64(key, secret, base64, md5);
} catch (Exception e) {
return new SendRespones(500, e.getMessage());
}
}
public static SendRespones sendNews(String key, String secret, java.util.List<Map<String, String>> articles) {
Map<String, Object> body = new HashMap<>();
body.put("msgtype", "news");
Map<String, Object> news = new HashMap<>();
news.put("articles", articles);
body.put("news", news);
return doSend(buildSignedUrl(key, secret), body);
}
public static SendRespones sendFile(String key, String secret, byte[] fileBytes, String fileName) {
try {
String uploadUrl = QYWX_ORIGIN + "/cgi-bin/webhook/upload_media?key=" + key + "&type=file";
String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
String contentType = "multipart/form-data; boundary=" + boundary;
StringBuilder sb = new StringBuilder();
sb.append("--").append(boundary).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"media\"; filename=\"")
.append(fileName).append("\"\r\n");
sb.append("Content-Type: application/octet-stream\r\n\r\n");
byte[] prefix = sb.toString().getBytes(StandardCharsets.UTF_8);
byte[] suffix = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
byte[] body = new byte[prefix.length + fileBytes.length + suffix.length];
System.arraycopy(prefix, 0, body, 0, prefix.length);
System.arraycopy(fileBytes, 0, body, prefix.length, fileBytes.length);
System.arraycopy(suffix, 0, body, prefix.length + fileBytes.length, suffix.length);
String responseStr = HttpRequest.post(uploadUrl)
.header("Content-Type", contentType)
.body(body)
.execute().body();
logger.info("文件上传响应: {}", responseStr);
com.alibaba.fastjson2.JSONObject obj = JSON.parseObject(responseStr);
String mediaId = obj.getString("media_id");
if (mediaId == null) {
return new SendRespones(500, obj.getString("errmsg"));
}
Map<String, Object> sendBody = new HashMap<>();
sendBody.put("msgtype", "file");
Map<String, Object> file = new HashMap<>();
file.put("media_id", mediaId);
sendBody.put("file", file);
return doSend(buildSignedUrl(key, secret), sendBody);
} catch (Exception e) {
return new SendRespones(500, e.getMessage());
}
}
public static SendRespones sendTemplateCard(String key, String secret, Map<String, Object> card) {
Map<String, Object> body = new HashMap<>();
body.put("msgtype", "template_card");
body.put("template_card", card);
return doSend(buildSignedUrl(key, secret), body);
}
private static SendRespones doSend(String url, Map<String, Object> body) {
String json = JSON.toJSONString(body);
logger.info("企业微信 Webhook 请求: url={}, body={}", url, json);
String responseStr = post(url).body(json).execute().body();
logger.info("企业微信 Webhook 响应: {}", responseStr);
try {
SendRespones resp = JSON.parseObject(responseStr, SendRespones.class);
if (resp == null) {
return new SendRespones(500, "empty response");
}
return resp;
} catch (Exception e) {
return new SendRespones(500, e.getMessage());
}
}
private static String hmacSha256Base64(String secret, String data) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] signData = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return java.util.Base64.getEncoder().encodeToString(signData);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}