如何用Quarkus REST Client向Trello API发送带二进制文件的multipart/form-data
解决Quarkus中向Trello卡片发送二进制图片附件的问题
问题概述
正在将旧Java软件迁移至基于RESTEasy Reactive/MicroProfile的Quarkus框架,需实现向Trello卡片添加图片的功能。要求发送包含name、filename、mimeType、setCover及二进制文件的multipart/form-data,核心要求是文件以二进制形式发送(Trello官方文档存在疏漏,不可完全采信)。
可正常运行的旧代码
旧Java代码通过JAX-RS Client实现功能:
String name = "Cover.jpg"; File tempImage = new File(name); try { Client client = ClientBuilder.newClient(new ClientConfig()) .register(MultiPartFeature.class); String mime = "image/jpg"; byte[] buffer = Files.readAllBytes(Paths.get("/PATH/" + name)); // 保存临时文件 FileOutputStream fos = new FileOutputStream(tempImage); fos.write(buffer); fos.flush(); fos.close(); FileDataBodyPart filePart = new FileDataBodyPart("file", tempImage); FormDataMultiPart formDataMultiPart = new FormDataMultiPart(); final FormDataMultiPart multipart = (FormDataMultiPart) formDataMultiPart .field("mimeType", mime) .field("setCover", "true").field("name", name).bodyPart(filePart); // 添加封面 WebTarget coverService = client.target(URI.create("https://api.trello.com/1/cards/CARD_ID/attachments")); String coverResponse = coverService.queryParam("key", "KEY").queryParam("token", "TOKEN").request() .header("Accept", "application/json") .post(Entity.entity(multipart, multipart.getMediaType()), String.class); } catch (Exception e) { e.printStackTrace(); }
旧代码生成的请求示例
1 * Sending client request on thread main 1 > POST https://api.trello.com/1/cards/CARD_ID/attachments?key=KEY&token=TOKEN 1 > Accept: application/json 1 > Content-Type: multipart/form-data --Boundary_1_2089589253_1678121120116 Content-Type: text/plain Content-Disposition: form-data; name="mimeType" image/jpg --Boundary_1_2089589253_1678121120116 Content-Type: text/plain Content-Disposition: form-data; name="setCover" true --Boundary_1_2089589253_1678121120116 Content-Type: text/plain Content-Disposition: form-data; name="name" Cover.jpg --Boundary_1_2089589253_1678121120116 Content-Type: image/jpeg Content-Disposition: form-data; filename="Cover.jpg"; modification-date="Mon, 06 Mar 2023 16:45:19 GMT"; size=105034; name="file" <BINARY! content> --Boundary_1_2089589253_1678121120116--
尝试过的失败Quarkus代码
REST Client接口
@Path(ADD_ATTACHMENT_CARD_URL) @POST @javax.ws.rs.Consumes(MediaType.MULTIPART_FORM_DATA) @Consumes(MediaType.MULTIPART_FORM_DATA) @ClientHeaderParam(name = "Accept", value = MediaType.APPLICATION_JSON) @ClientHeaderParam(name = "User-Agent", value = "FawnKeeper") public void setCover(@QueryParam("key") String key, @QueryParam("token") String token, @PathParam("id") String id,@MultipartForm MultipartBody file);
MultipartBody类
public class MultipartBody implements Serializable { @PartType(MediaType.TEXT_PLAIN) public String mimeType = "image/jpg"; @PartType(MediaType.TEXT_PLAIN) public String setCover = "false"; @PartType(MediaType.TEXT_PLAIN) public String name = "Cover.jpg"; @PartType("image/jpeg") public File file; }
核心问题:文件被转为Base64而非二进制发送,且存在转换器错误。
可行解决方案
方案一:修复REST Client的Multipart处理
问题根源在于使用File类型会被默认序列化为Base64,改用InputStream并补充必要注解即可解决:
1. 修改MultipartBody类
import jakarta.ws.rs.FormParam; import jakarta.ws.rs.core.MediaType; import org.jboss.resteasy.annotations.providers.multipart.PartFilename; import org.jboss.resteasy.annotations.providers.multipart.PartType; import java.io.InputStream; import java.io.Serializable; public class MultipartBody implements Serializable { @FormParam("mimeType") @PartType(MediaType.TEXT_PLAIN) public String mimeType = "image/jpg"; @FormParam("setCover") @PartType(MediaType.TEXT_PLAIN) public String setCover = "true"; @FormParam("name") @PartType(MediaType.TEXT_PLAIN) public String name = "Cover.jpg"; @FormParam("file") @PartType(MediaType.APPLICATION_OCTET_STREAM) @PartFilename("Cover.jpg") // 指定文件名 public InputStream fileInputStream; }
2. 修正REST Client接口
移除重复的@Consumes注解,确保参数绑定正确:
import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; @RegisterRestClient(baseUri = "https://api.trello.com/1") public interface TrelloClient { String ADD_ATTACHMENT_CARD_URL = "/cards/{id}/attachments"; @Path(ADD_ATTACHMENT_CARD_URL) @POST @Consumes(MediaType.MULTIPART_FORM_DATA) @ClientHeaderParam(name = "Accept", value = MediaType.APPLICATION_JSON) @ClientHeaderParam(name = "User-Agent", value = "FawnKeeper") void setCover(@QueryParam("key") String key, @QueryParam("token") String token, @PathParam("id") String id, @MultipartForm MultipartBody requestBody); }
3. 使用示例
import jakarta.inject.Inject; import org.eclipse.microprofile.rest.client.inject.RestClient; import java.io.FileInputStream; import java.io.IOException; public class TrelloService { @Inject @RestClient TrelloClient trelloClient; public void addCoverToCard(String cardId, String apiKey, String apiToken, String filePath) throws IOException { MultipartBody body = new MultipartBody(); body.setCover = "true"; body.fileInputStream = new FileInputStream(filePath); trelloClient.setCover(apiKey, apiToken, cardId, body); } }
方案二:使用Vert.x Web Client实现
如果REST Client方案仍有问题,可直接用Vert.x Web Client手动构建multipart请求,确保二进制发送:
import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.multipart.FormData; import jakarta.inject.Inject; import java.nio.file.Files; import java.nio.file.Paths; public class VertxTrelloService { @Inject Vertx vertx; public void addCoverToCard(String cardId, String apiKey, String apiToken, String filePath) throws Exception { WebClient client = WebClient.create(vertx); // 读取文件为二进制Buffer byte[] imageBytes = Files.readAllBytes(Paths.get(filePath)); Buffer imageBuffer = Buffer.buffer(imageBytes); // 构建multipart表单 FormData formData = FormData.create() .text("mimeType", "image/jpg") .text("setCover", "true") .text("name", "Cover.jpg") .binary("file", imageBuffer, "Cover.jpg", "image/jpeg"); // 发送请求 client.postAbs("https://api.trello.com/1/cards/" + cardId + "/attachments") .addQueryParam("key", apiKey) .addQueryParam("token", apiToken) .putHeader("Accept", "application/json") .putHeader("User-Agent", "FawnKeeper") .sendMultipartForm(formData, ar -> { if (ar.succeeded()) { System.out.println("附件添加成功:" + ar.result().bodyAsString()); } else { ar.cause().printStackTrace(); } }); } }
内容的提问来源于stack exchange,提问作者J. Reisner
相关产品推荐
相关产品推荐

