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; import org.springframework.http.MediaType; 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; import java.util.List; @RestController @RequestMapping("/v1/integrations/wecom") public class WecomCallbackController { private final FoundationProperties properties; private final ChatService chatService; private final WecomClient wecomClient; private final WecomCryptoService wecomCryptoService; private final XmlMapper xmlMapper = new XmlMapper(); public WecomCallbackController( FoundationProperties properties, ChatService chatService, WecomClient wecomClient, WecomCryptoService wecomCryptoService ) { this.properties = properties; this.chatService = chatService; this.wecomClient = wecomClient; this.wecomCryptoService = wecomCryptoService; } @GetMapping("/callback") public String verify( @RequestParam("msg_signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr ) { String payload = wecomCryptoService.isSafeModeEnabled() ? echostr : null; if (!wecomCryptoService.validateSignature(signature, timestamp, nonce, payload)) { throw new IllegalArgumentException("Invalid WeCom signature"); } return wecomCryptoService.isSafeModeEnabled() ? wecomCryptoService.decrypt(echostr) : echostr; } @PostMapping(value = "/callback", consumes = {MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_XML_VALUE}) public String receive( @RequestParam("msg_signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestBody String body ) throws Exception { WecomIncomingMessage msg = parseIncomingMessage(signature, timestamp, nonce, body); if (msg == null || msg.getMsgType() == null || !"text".equalsIgnoreCase(msg.getMsgType())) { return "success"; } String userText = msg.getContent() == null ? "" : msg.getContent().trim(); if (userText.isBlank()) { return "success"; } ChatRequest req = new ChatRequest( null, List.of( new ChatRequest.Message("system", "You are an enterprise CRM assistant. Provide concise, actionable follow-up advice."), new ChatRequest.Message("user", userText) ), 0.3 ); ChatResponse chat = chatService.chat(new AgentContext(properties.getWecom().getCrmAgentId()), req); String reply = (chat.content() == null || chat.content().isBlank()) ? "Request received, but I could not generate advice right now." : chat.content(); wecomClient.sendText(msg.getFromUserName(), reply); return "success"; } 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()) { throw new IllegalArgumentException("Missing WeCom encrypted payload"); } if (!wecomCryptoService.validateSignature(signature, timestamp, nonce, envelope.getEncrypt())) { throw new IllegalArgumentException("Invalid WeCom signature"); } String plainXml = wecomCryptoService.decrypt(envelope.getEncrypt()); return xmlMapper.readValue(plainXml, WecomIncomingMessage.class); } if (!wecomCryptoService.validateSignature(signature, timestamp, nonce, null)) { throw new IllegalArgumentException("Invalid WeCom signature"); } return xmlMapper.readValue(body, WecomIncomingMessage.class); } }