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

Quarkus环境下通过内部中转API向Redmine API上传文件的实现问题

Quarkus环境下通过内部中转API向Redmine API上传文件的实现问题

兄弟,太懂你这种中转文件卡壳的憋屈了!之前帮朋友调过几乎一模一样的Quarkus中转上传场景,踩了好几个坑,给你捋几个靠谱的思路和代码示例,应该能帮你搞定。

首先得明确:Redmine的文件上传接口要求用multipart/form-data格式,核心参数是attachments[][file](用来传文件),还要带上issue_id这类必要参数。咱们的中转API必须严格按照这个格式把请求原封不动(或者对应上参数名)转发给Redmine,这是核心前提。

下面给你两种亲测有效的实现方式:

方式一:直接接收Multipart请求并转发

这种方式最直观,前端传过来的是multipart格式,咱们直接接过来再转交给内部中转API。

1. 编写文件上传的Resource类

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.reactive.MultipartFormData;
import org.jboss.resteasy.reactive.FilePart;

@Path("/proxy-redmine-upload")
public class RedmineUploadProxyResource {

    // 注入调用内部中转API的RestClient
    @RestClient
    private InternalRedmineProxyClient internalProxyClient;

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_JSON)
    public Response proxyFileUpload(MultipartFormData formData) {
        // 从前端请求里拿必要参数,比如issue_id
        String issueId = formData.getFormDataPart("issue_id", String.class, null);
        // 获取上传的文件部分
        FilePart filePart = formData.getFile("file").get();

        // 构造要转发给内部API的multipart数据,严格对应Redmine要求的参数名
        MultipartFormData.Builder forwardFormBuilder = MultipartFormData.create();
        forwardFormBuilder.field("issue_id", issueId);
        // 注意这里的参数名是Redmine要求的attachments[][file]
        forwardFormBuilder.file("attachments[][file]", 
                               filePart.getFileName(), 
                               filePart.getMediaType(), 
                               filePart.getInputStream());

        // 调用内部中转API
        Response redmineResp = internalProxyClient.forwardToRedmine(forwardFormBuilder.build());
        // 把Redmine的响应原封不动返回给前端
        return Response.status(redmineResp.getStatus())
                .entity(redmineResp.readEntity(String.class))
                .build();
    }
}

2. 定义内部中转API的RestClient接口

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.MultipartFormData;

@RegisterRestClient(baseUri = "http://你的内部中转API地址")
public interface InternalRedmineProxyClient {

    @POST
    @Path("/redmine-api-proxy/upload")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_JSON)
    Response forwardToRedmine(MultipartFormData formData);
}

方式二:接收InputStream再构造Multipart转发

如果你之前是用InputStream接收前端文件的,那可以手动构造符合要求的multipart请求再转发:

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.HeaderParam;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.reactive.MultipartFormData;
import java.io.InputStream;

@Path("/upload-stream-proxy")
public class StreamUploadProxyResource {

    @RestClient
    private InternalRedmineProxyClient internalProxyClient;

    @POST
    @Consumes(MediaType.APPLICATION_OCTET_STREAM)
    @Produces(MediaType.APPLICATION_JSON)
    public Response proxyStreamUpload(@HeaderParam("Content-Disposition") String contentDisposition,
                                      @HeaderParam("X-Issue-Id") String issueId,
                                      InputStream fileStream) {
        // 从Content-Disposition头里解析文件名,格式一般是form-data; name="file"; filename="test.pdf"
        String fileName = contentDisposition.split("filename=")[1].replace("\"", "");

        // 手动构造Redmine需要的multipart格式
        MultipartFormData formData = MultipartFormData.create()
                .field("issue_id", issueId)
                .file("attachments[][file]", fileName, MediaType.APPLICATION_OCTET_STREAM_TYPE, fileStream)
                .build();

        Response redmineResp = internalProxyClient.forwardToRedmine(formData);
        return Response.status(redmineResp.getStatus())
                .entity(redmineResp.readEntity(String.class))
                .build();
    }
}

几个必须注意的坑

  • 依赖要加全:Quarkus处理multipart和RestClient转发需要对应的扩展,pom.xml里一定要加:
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive-multipart</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-reactive-multipart</artifactId>
</dependency>
  • 不要乱转格式:别把文件转成base64再传,Redmine不吃这套,必须用二进制的multipart格式直接转发。
  • 资源泄漏要避免:如果手动处理InputStream,最好用try-with-resources包裹,Quarkus虽然会自动处理,但自己注意更稳妥。
  • 认证信息要传递:如果内部中转API需要认证(比如Redmine的API密钥),要么在RestClient里通过@HeaderParam加,要么写个ClientRequestFilter统一处理。

你之前试的InputStream方式大概率是没正确构造Redmine要求的multipart格式才失败的,按照上面的示例调整一下,应该就能顺利中转了!

备注:内容来源于stack exchange,提问作者Wyatt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 16:44:39