184 lines
7.6 KiB
Java
184 lines
7.6 KiB
Java
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) + "×tamp=" + 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);
|
||
}
|
||
}
|
||
}
|
||
|
||
|