You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring RestTemplate POST请求异常但Postman正常,Mandrill Webhook适配问题

解决Spring中RestTemplate转发Mandrill Webhook请求异常的问题

我之前也碰到过类似Mandrill Webhook和Spring适配的坑,给你几个排查方向和实际解决方案,应该能帮你搞定:

1. 先检查RestTemplate的核心配置问题

RestTemplate发送JSON请求失败,大概率是消息转换器或请求头配置不对:

  • 确保注册了JSON消息转换器:如果是自定义的RestTemplate,一定要手动添加MappingJackson2HttpMessageConverter,否则Spring不知道怎么把Java对象序列化成JSON:
    RestTemplate restTemplate = new RestTemplate();
    // 优先添加Jackson转换器,避免被其他转换器覆盖
    restTemplate.getMessageConverters().add(0, new MappingJackson2HttpMessageConverter());
    
  • 强制设置请求头为application/json:很多时候忘记显式设置Content-Type,导致Spring默认用表单格式发送,后端自然解析失败:
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    // 把解析后的Mandrill事件对象封装成请求实体
    HttpEntity<List<MandrillEvent>> requestEntity = new HttpEntity<>(parsedEvents, headers);
    // 发送请求
    restTemplate.postForObject("http://localhost:8080/your-json-endpoint", requestEntity, String.class);
    

2. 确认Mandrill原始请求的解析是否正确

Mandrill的application/x-www-form-urlencoded请求里,event参数本身就是一个JSON字符串,你需要先把它解析成Java对象再转发,不能直接把原始参数丢给RestTemplate:

// 从原始表单请求中拿到event参数的JSON字符串
String eventJson = request.getParameter("event");
ObjectMapper objectMapper = new ObjectMapper();
// 解析成对应的事件列表(根据Mandrill的事件结构定义你的MandrillEvent类)
List<MandrillEvent> parsedEvents = objectMapper.readValue(
    eventJson, 
    new TypeReference<List<MandrillEvent>>() {}
);

如果这一步解析出错,转发的请求体本身就是无效JSON,后端肯定会返回400错误。

3. 排查接收端点的配置

确保你的JSON接收端点能正确处理请求:

  • 必须添加@RequestBody注解,告诉Spring从请求体中解析JSON:
    @PostMapping(value = "/your-json-endpoint", consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Void> handleConvertedWebhook(@RequestBody List<MandrillEvent> events) {
        // 你的业务逻辑
        return ResponseEntity.ok().build();
    }
    
  • 不要限制Content-Type为非JSON类型,否则会直接拒绝RestTemplate的请求。

4. 捕获异常细节,精准定位问题

你只说RestTemplate异常,但没说具体是什么异常,这是排查关键。建议捕获异常并打印响应内容:

try {
    restTemplate.postForObject(url, requestEntity, String.class);
} catch (HttpStatusCodeException e) {
    // 打印状态码和响应体,后端会告诉你哪里错了
    System.err.println("请求失败,状态码:" + e.getStatusCode());
    System.err.println("错误响应内容:" + e.getResponseBodyAsString());
} catch (JsonProcessingException e) {
    // 序列化失败,说明你的MandrillEvent类和JSON结构不匹配
    e.printStackTrace();
}

比如如果响应体提示“字段xxx不存在”,就是你的实体类和Mandrill的事件字段不对应;如果是403,可能是签名验证的问题。

5. 更优方案:直接在原始端点处理表单请求

其实没必要转发请求,你可以直接在接收Mandrill Webhook的端点里处理application/x-www-form-urlencoded请求,同时完成签名验证:

@PostMapping(
    value = "/mandrill-webhook", 
    consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public ResponseEntity<Void> handleMandrillWebhook(
        @RequestHeader("X-Mandrill-Signature") String signature,
        @RequestParam("event") String eventJson,
        HttpServletRequest request) {
    // 1. 验证Mandrill签名(按官方文档实现)
    boolean isSignatureValid = verifyMandrillSignature(
        signature, 
        request.getRequestURL().toString(), 
        request.getParameterMap(), 
        "你的Mandrill API密钥"
    );
    if (!isSignatureValid) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
    }
    // 2. 解析事件并处理
    ObjectMapper objectMapper = new ObjectMapper();
    List<MandrillEvent> events = objectMapper.readValue(
        eventJson, 
        new TypeReference<List<MandrillEvent>>() {}
    );
    // 你的业务逻辑...
    return ResponseEntity.ok().build();
}

签名验证的逻辑参考Mandrill官方:把API密钥、请求URL、所有请求参数按特定顺序拼接,用HMAC-SHA1加密后和请求头的签名对比即可。

内容的提问来源于stack exchange,提问作者CapitanFindus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:00:11