Spring Boot中RestTemplate调用Telegram API sendDocument报错解决
问题背景
使用Spring Boot 3.1、Telegram Bots Spring Boot Starter 6.7.0搭建WebHook机器人,业务要求必须通过RestTemplate调用Telegram API的/sendDocument接口发送二进制PDF文件。之前编写的RestTemplate代码触发Telegram API 400错误:
{"ok":false,"error_code":400,"description":"Bad Request: invalid file HTTP URL specified: Unsupported URL protocol"}
同时伴随空指针异常:Cannot invoke "org.telegram.telegrambots.meta.api.objects.Message.getChatId()" because the return value of "org.telegram.telegrambots.meta.api.objects.Update.getMessage()" is null。
改用Apache HttpClient或正确的WebClient代码可正常运行,但需要适配RestTemplate实现。
错误原因
之前的代码错误使用了Telegram SDK的InputFile对象作为Multipart请求体的文件参数。RestTemplate无法正确序列化InputFile,导致请求体中传递的是InputFile的toString()值(类似文件URL格式),而非实际的二进制文件内容,触发Telegram API的参数格式错误。
解决方案
RestTemplate处理Multipart文件上传时,需要使用Spring的FileSystemResource或ClassPathResource直接包装文件,而非Telegram SDK的InputFile。同时确保RestTemplate的配置支持Multipart请求(Spring Boot默认配置已支持,但如果是自定义RestTemplate需确保包含MultipartMessageConverter)。
正确代码实现
1. 基础RestTemplate调用代码
import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import java.io.IOException; public void sendPdfViaRestTemplate(Long chatId, String botToken) { String apiUrl = "https://api.telegram.org/bot" + botToken + "/sendDocument"; // 可注入Spring容器中配置好的RestTemplate,无需手动实例化 RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("chat_id", chatId); // 使用ClassPathResource读取classpath下的PDF文件 try { ClassPathResource pdfResource = new ClassPathResource("reports/report.pdf"); body.add("document", pdfResource); } catch (IOException e) { throw new RuntimeException("Failed to load PDF file", e); } HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers); // 执行请求,可根据业务需求替换返回类型(如Telegram官方API的响应对象) String response = restTemplate.postForObject(apiUrl, requestEntity, String.class); System.out.println("Telegram API Response: " + response); }
2. 自定义RestTemplate配置(可选)
如果需要自定义RestTemplate(如设置超时、添加拦截器),需确保配置MultipartMessageConverter以支持Multipart请求:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.multipart.MultipartMessageConverter; import org.springframework.web.client.RestTemplate; import java.util.List; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); // 确保MultipartMessageConverter存在(Spring Boot默认已包含,此处为显式配置示例) List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters(); boolean hasMultipartConverter = converters.stream() .anyMatch(converter -> converter instanceof MultipartMessageConverter); if (!hasMultipartConverter) { converters.add(new MultipartMessageConverter()); } // 可添加其他配置,比如设置请求超时 // SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); // factory.setConnectTimeout(5000); // factory.setReadTimeout(10000); // restTemplate.setRequestFactory(factory); return restTemplate; } }
注意事项
- 禁止使用Telegram SDK的
InputFile作为RestTemplate的Multipart参数,必须直接使用Spring的Resource实现类传递文件内容。 - 若文件存储在本地磁盘而非classpath下,改用
FileSystemResource包装文件路径:body.add("document", new FileSystemResource("/path/to/your/report.pdf")); - 空指针异常与WebHook的Update对象解析有关,需在WebHook处理代码中添加空值判断:
if (update.getMessage() != null) { Long chatId = update.getMessage().getChatId(); sendPdfViaRestTemplate(chatId, botToken); } else { // 处理无Message的Update(如回调查询等) }
内容的提问来源于stack exchange,提问作者War1ock

