package com.ruoyi.jarvis.wecom; import com.alibaba.fastjson2.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; /** * 调用 wxSend 的企微应用文本主动推送(POST /wecom/active-push)。 */ @Component public class WxSendWeComPushClient { private static final Logger log = LoggerFactory.getLogger(WxSendWeComPushClient.class); public static final String HEADER_PUSH_SECRET = "X-WxSend-WeCom-Push-Secret"; @Value("${jarvis.wecom.wxsend-base-url:}") private String wxsendBaseUrl; @Value("${jarvis.wecom.push-secret:}") private String pushSecret; /** * 在被动回复返回后延迟再发,保证企微侧先出现首条被动消息。 */ public void scheduleActivePushes(String toUser, List contents) { if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(pushSecret) || !StringUtils.hasText(toUser) || contents == null || contents.isEmpty()) { return; } final String userId = toUser.trim(); final List list = new ArrayList<>(contents); CompletableFuture.runAsync(() -> { try { Thread.sleep(450); String base = normalizeBase(wxsendBaseUrl); String url = base + "/wecom/active-push"; for (String c : list) { if (!StringUtils.hasText(c)) { continue; } postJson(url, userId, c.trim()); Thread.sleep(120); } } catch (Exception e) { log.warn("企微主动推送任务异常 userId={} msg={}", userId, e.toString()); } }); } private static String normalizeBase(String base) { String b = base.trim(); if (b.endsWith("/")) { return b.substring(0, b.length() - 1); } return b; } private void postJson(String url, String toUser, String content) { JSONObject body = new JSONObject(); body.put("toUser", toUser); body.put("content", content); byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8); HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod("POST"); conn.setConnectTimeout(15000); conn.setReadTimeout(60000); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); conn.setRequestProperty(HEADER_PUSH_SECRET, pushSecret); try (OutputStream os = conn.getOutputStream()) { os.write(bytes); } int code = conn.getResponseCode(); InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream(); String resp = readAll(is); if (code < 200 || code >= 300) { log.warn("wxSend active-push HTTP {} body={}", code, resp); } else { log.debug("wxSend active-push OK http={} resp={}", code, resp); } } catch (Exception e) { log.warn("wxSend active-push 请求失败 url={} err={}", url, e.toString()); } finally { if (conn != null) { conn.disconnect(); } } } private static String readAll(InputStream is) throws java.io.IOException { if (is == null) { return ""; } byte[] buf = new byte[4096]; StringBuilder sb = new StringBuilder(); int n; while ((n = is.read(buf)) >= 0) { sb.append(new String(buf, 0, n, StandardCharsets.UTF_8)); } return sb.toString(); } }