package com.company.agentfoundation.integration.wecom; import com.company.agentfoundation.config.FoundationProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import java.time.Instant; @Service public class WecomClient { private static final Logger log = LoggerFactory.getLogger(WecomClient.class); private final FoundationProperties properties; private final WebClient webClient; private volatile String cachedToken; private volatile Instant tokenExpireAt = Instant.EPOCH; public WecomClient(FoundationProperties properties, WebClient.Builder builder) { this.properties = properties; this.webClient = builder.baseUrl("https://qyapi.weixin.qq.com").build(); } public void sendText(String toUser, String content) { log.info("wecom sendText start toUser={} agentId={} contentPreview={}", toUser, properties.getWecom().getAgentId(), preview(content)); String token = getAccessToken(); WecomSendMessageRequest req = new WecomSendMessageRequest(); req.setToUser(toUser); req.setAgentId(properties.getWecom().getAgentId()); WecomSendMessageRequest.Text text = new WecomSendMessageRequest.Text(); text.setContent(content); req.setText(text); WecomApiResponse response = webClient.post() .uri(uriBuilder -> uriBuilder.path("/cgi-bin/message/send").queryParam("access_token", token).build()) .contentType(MediaType.APPLICATION_JSON) .bodyValue(req) .retrieve() .bodyToMono(WecomApiResponse.class) .block(); if (response == null || !response.success()) { log.error("wecom sendText failed toUser={} err={}", toUser, response == null ? "null" : response.errmsg()); throw new IllegalStateException("WeCom send failed: " + (response == null ? "null" : response.errmsg())); } log.info("wecom sendText success toUser={}", toUser); } private String getAccessToken() { if (cachedToken != null && Instant.now().isBefore(tokenExpireAt)) { log.info("wecom gettoken use cached token expireAt={}", tokenExpireAt); return cachedToken; } WecomTokenResponse response = webClient.get() .uri(uriBuilder -> uriBuilder.path("/cgi-bin/gettoken") .queryParam("corpid", properties.getWecom().getCorpId()) .queryParam("corpsecret", properties.getWecom().getSecret()) .build()) .retrieve() .bodyToMono(WecomTokenResponse.class) .block(); if (response == null || !response.success() || response.accessToken() == null) { log.error("wecom gettoken failed err={}", response == null ? "null" : response.errmsg()); throw new IllegalStateException("WeCom gettoken failed: " + (response == null ? "null" : response.errmsg())); } cachedToken = response.accessToken(); int expires = response.expiresIn() == null ? 7200 : response.expiresIn(); tokenExpireAt = Instant.now().plusSeconds(Math.max(300, expires - 120)); log.info("wecom gettoken success expireAt={}", tokenExpireAt); return cachedToken; } private String preview(String text) { if (text == null || text.isBlank()) { return ""; } return text.length() <= 80 ? text : text.substring(0, 80) + "..."; } }