136 lines
6.3 KiB
Java
Raw Normal View History

2026-06-01 19:09:40 +08:00
package com.company.agentfoundation.integration.wecom;
import com.company.agentfoundation.config.FoundationProperties;
import com.company.agentfoundation.domain.ChatRequest;
import com.company.agentfoundation.domain.ChatResponse;
import com.company.agentfoundation.security.AgentContext;
import com.company.agentfoundation.service.ChatService;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
2026-06-02 11:55:42 +08:00
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
2026-06-01 19:09:40 +08:00
import org.springframework.http.MediaType;
2026-06-02 10:27:21 +08:00
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
2026-06-01 19:09:40 +08:00
import java.util.List;
@RestController
@RequestMapping("/v1/integrations/wecom")
public class WecomCallbackController {
2026-06-02 11:55:42 +08:00
private static final Logger log = LoggerFactory.getLogger(WecomCallbackController.class);
2026-06-01 19:09:40 +08:00
private final FoundationProperties properties;
private final ChatService chatService;
private final WecomClient wecomClient;
2026-06-02 10:27:21 +08:00
private final WecomCryptoService wecomCryptoService;
2026-06-01 19:09:40 +08:00
private final XmlMapper xmlMapper = new XmlMapper();
2026-06-02 10:27:21 +08:00
public WecomCallbackController(
FoundationProperties properties,
ChatService chatService,
WecomClient wecomClient,
WecomCryptoService wecomCryptoService
) {
2026-06-01 19:09:40 +08:00
this.properties = properties;
this.chatService = chatService;
this.wecomClient = wecomClient;
2026-06-02 10:27:21 +08:00
this.wecomCryptoService = wecomCryptoService;
2026-06-01 19:09:40 +08:00
}
@GetMapping("/callback")
2026-06-02 10:27:21 +08:00
public String verify(
@RequestParam("msg_signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr
) {
2026-06-02 11:55:42 +08:00
log.info("wecom callback verify received safeMode={} timestamp={} nonce={}", wecomCryptoService.isSafeModeEnabled(), timestamp, nonce);
2026-06-02 10:27:21 +08:00
String payload = wecomCryptoService.isSafeModeEnabled() ? echostr : null;
if (!wecomCryptoService.validateSignature(signature, timestamp, nonce, payload)) {
2026-06-02 11:55:42 +08:00
log.warn("wecom callback verify signature invalid timestamp={} nonce={}", timestamp, nonce);
2026-06-01 19:09:40 +08:00
throw new IllegalArgumentException("Invalid WeCom signature");
}
2026-06-02 11:55:42 +08:00
log.info("wecom callback verify success safeMode={}", wecomCryptoService.isSafeModeEnabled());
2026-06-02 10:27:21 +08:00
return wecomCryptoService.isSafeModeEnabled() ? wecomCryptoService.decrypt(echostr) : echostr;
2026-06-01 19:09:40 +08:00
}
@PostMapping(value = "/callback", consumes = {MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_XML_VALUE})
2026-06-02 10:27:21 +08:00
public String receive(
@RequestParam("msg_signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestBody String body
) throws Exception {
2026-06-02 11:55:42 +08:00
log.info("wecom callback message received safeMode={} timestamp={} nonce={} bodyLength={}",
wecomCryptoService.isSafeModeEnabled(), timestamp, nonce, body == null ? 0 : body.length());
2026-06-02 10:27:21 +08:00
WecomIncomingMessage msg = parseIncomingMessage(signature, timestamp, nonce, body);
2026-06-02 11:55:42 +08:00
log.info("wecom callback parsed fromUser={} toUser={} msgType={} contentPreview={}",
msg == null ? null : msg.getFromUserName(),
msg == null ? null : msg.getToUserName(),
msg == null ? null : msg.getMsgType(),
preview(msg == null ? null : msg.getContent()));
2026-06-01 19:09:40 +08:00
if (msg == null || msg.getMsgType() == null || !"text".equalsIgnoreCase(msg.getMsgType())) {
2026-06-02 11:55:42 +08:00
log.info("wecom callback ignored non-text message");
2026-06-01 19:09:40 +08:00
return "success";
}
String userText = msg.getContent() == null ? "" : msg.getContent().trim();
if (userText.isBlank()) {
2026-06-02 11:55:42 +08:00
log.info("wecom callback ignored blank text");
2026-06-01 19:09:40 +08:00
return "success";
}
ChatRequest req = new ChatRequest(
null,
List.of(
2026-06-02 10:27:21 +08:00
new ChatRequest.Message("system", "You are an enterprise CRM assistant. Provide concise, actionable follow-up advice."),
2026-06-01 19:09:40 +08:00
new ChatRequest.Message("user", userText)
),
0.3
);
2026-06-02 11:55:42 +08:00
log.info("wecom callback invoking crm agent agentId={} fromUser={}", properties.getWecom().getCrmAgentId(), msg.getFromUserName());
2026-06-01 19:09:40 +08:00
ChatResponse chat = chatService.chat(new AgentContext(properties.getWecom().getCrmAgentId()), req);
2026-06-02 10:27:21 +08:00
String reply = (chat.content() == null || chat.content().isBlank())
? "Request received, but I could not generate advice right now."
: chat.content();
2026-06-02 11:55:42 +08:00
log.info("wecom callback agent response traceId={} replyPreview={}", chat.traceId(), preview(reply));
2026-06-01 19:09:40 +08:00
wecomClient.sendText(msg.getFromUserName(), reply);
2026-06-02 11:55:42 +08:00
log.info("wecom callback reply sent toUser={}", msg.getFromUserName());
2026-06-01 19:09:40 +08:00
return "success";
}
2026-06-02 10:27:21 +08:00
private WecomIncomingMessage parseIncomingMessage(String signature, String timestamp, String nonce, String body) throws Exception {
if (wecomCryptoService.isSafeModeEnabled()) {
WecomEncryptedEnvelope envelope = xmlMapper.readValue(body, WecomEncryptedEnvelope.class);
if (envelope == null || envelope.getEncrypt() == null || envelope.getEncrypt().isBlank()) {
2026-06-02 11:55:42 +08:00
log.warn("wecom callback encrypted envelope missing encrypt field");
2026-06-02 10:27:21 +08:00
throw new IllegalArgumentException("Missing WeCom encrypted payload");
}
if (!wecomCryptoService.validateSignature(signature, timestamp, nonce, envelope.getEncrypt())) {
2026-06-02 11:55:42 +08:00
log.warn("wecom callback encrypted signature invalid timestamp={} nonce={}", timestamp, nonce);
2026-06-02 10:27:21 +08:00
throw new IllegalArgumentException("Invalid WeCom signature");
}
String plainXml = wecomCryptoService.decrypt(envelope.getEncrypt());
2026-06-02 11:55:42 +08:00
log.info("wecom callback decrypt success plainXmlLength={}", plainXml.length());
2026-06-02 10:27:21 +08:00
return xmlMapper.readValue(plainXml, WecomIncomingMessage.class);
2026-06-01 19:09:40 +08:00
}
2026-06-02 10:27:21 +08:00
if (!wecomCryptoService.validateSignature(signature, timestamp, nonce, null)) {
2026-06-02 11:55:42 +08:00
log.warn("wecom callback plain signature invalid timestamp={} nonce={}", timestamp, nonce);
2026-06-02 10:27:21 +08:00
throw new IllegalArgumentException("Invalid WeCom signature");
2026-06-01 19:09:40 +08:00
}
2026-06-02 10:27:21 +08:00
return xmlMapper.readValue(body, WecomIncomingMessage.class);
2026-06-01 19:09:40 +08:00
}
2026-06-02 11:55:42 +08:00
private String preview(String text) {
if (text == null || text.isBlank()) {
return "";
}
return text.length() <= 80 ? text : text.substring(0, 80) + "...";
}
2026-06-01 19:09:40 +08:00
}