【问题标题】:How to handle org.springframework.core.io.Resource for Swagger Codegen如何处理 Swagger Codegen 的 org.springframework.core.io.Resource
【发布时间】:2018-03-10 16:18:50
【问题描述】:

我正在使用 Spring Boot 2 开发一个 Web 应用程序,其中有 2 个 Spring 项目协同工作:一个是 REST API 服务器,通过 REST API 为应用程序逻辑提供服务,另一个是负责呈现网页并调用API 服务器。 Web 项目使用 Swagger Codegen 自动生成调用 API 的类。

在 API 服务器中,我有一个控制器 ResourceController,其端点用于提供文件内容(即下载文件),如下所示

@GetMapping("/files/{uuid}")
@ResponseBody
public org.springframework.core.io.Resource getFile(@PathVariable String uuid) {
    String systemPath = fileService.getFilePath(uuid);
    return new FileSystemResource(systemPath);
}

在 web 客户端,Swagger 生成ResourceControllerApi,方法被翻译成

public io.swagger.client.model.Resource getFileUsingGET(String uuid) {...}

我想在 Web 项目中创建一个控制器,将用户浏览器之间的请求和响应传递到 API 服务器。我试过了

@GetMapping("/client/files/{uuid}")
@ResponseBody
public io.swagger.client.model.Resource getFile(@PathVariable String uuid) {
    return resourceControllerApi.getFileUsingGET(uuid);
}

调用 API(在客户端 Web)时,出现此错误

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class io.swagger.client.model.Resource] and content type [image/jpeg]

我希望当我输入http://myweb/client/files/dee38be4-6ef9-460d-bc44-f1b93770ab83 这样的网址时,浏览器会下载文件内容。我一直在寻找将io.swagger.client.model.Resource 转换为org.springframework.core.io.Resource 的方法,但无法弄清楚。

以下是自动生成的io.swagger.client.model.Resource的内容

/*
 * NPA Marketplace REST API
 * API to manage NPA Marketplace.
 *
 * OpenAPI spec version: 1.0
 * 
 *
 * NOTE: This class is auto generated by the swagger code generator program.
 * https://github.com/swagger-api/swagger-codegen.git
 * Do not edit the class manually.
 */


package io.swagger.client.model;

import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.client.model.InputStream;
import io.swagger.client.model.URI;
import io.swagger.client.model.URL;

/**
 * Resource
 */
@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-03-08T14:55:08.754+07:00")
public class Resource {
  @JsonProperty("description")
  private String description = null;

  @JsonProperty("file")
  private java.io.File file = null;

  @JsonProperty("filename")
  private String filename = null;

  @JsonProperty("inputStream")
  private InputStream inputStream = null;

  @JsonProperty("open")
  private Boolean open = null;

  @JsonProperty("readable")
  private Boolean readable = null;

  @JsonProperty("uri")
  private URI uri = null;

  @JsonProperty("url")
  private URL url = null;

  public Resource description(String description) {
    this.description = description;
    return this;
  }

   /**
   * Get description
   * @return description
  **/
  @ApiModelProperty(value = "")
  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public Resource file(java.io.File file) {
    this.file = file;
    return this;
  }

   /**
   * Get file
   * @return file
  **/
  @ApiModelProperty(value = "")
  public java.io.File getFile() {
    return file;
  }

  public void setFile(java.io.File file) {
    this.file = file;
  }

  public Resource filename(String filename) {
    this.filename = filename;
    return this;
  }

   /**
   * Get filename
   * @return filename
  **/
  @ApiModelProperty(value = "")
  public String getFilename() {
    return filename;
  }

  public void setFilename(String filename) {
    this.filename = filename;
  }

  public Resource inputStream(InputStream inputStream) {
    this.inputStream = inputStream;
    return this;
  }

   /**
   * Get inputStream
   * @return inputStream
  **/
  @ApiModelProperty(value = "")
  public InputStream getInputStream() {
    return inputStream;
  }

  public void setInputStream(InputStream inputStream) {
    this.inputStream = inputStream;
  }

  public Resource open(Boolean open) {
    this.open = open;
    return this;
  }

   /**
   * Get open
   * @return open
  **/
  @ApiModelProperty(value = "")
  public Boolean isOpen() {
    return open;
  }

  public void setOpen(Boolean open) {
    this.open = open;
  }

  public Resource readable(Boolean readable) {
    this.readable = readable;
    return this;
  }

   /**
   * Get readable
   * @return readable
  **/
  @ApiModelProperty(value = "")
  public Boolean isReadable() {
    return readable;
  }

  public void setReadable(Boolean readable) {
    this.readable = readable;
  }

  public Resource uri(URI uri) {
    this.uri = uri;
    return this;
  }

   /**
   * Get uri
   * @return uri
  **/
  @ApiModelProperty(value = "")
  public URI getUri() {
    return uri;
  }

  public void setUri(URI uri) {
    this.uri = uri;
  }

  public Resource url(URL url) {
    this.url = url;
    return this;
  }

   /**
   * Get url
   * @return url
  **/
  @ApiModelProperty(value = "")
  public URL getUrl() {
    return url;
  }

  public void setUrl(URL url) {
    this.url = url;
  }


  @Override
  public boolean equals(java.lang.Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Resource resource = (Resource) o;
    return Objects.equals(this.description, resource.description) &&
        Objects.equals(this.file, resource.file) &&
        Objects.equals(this.filename, resource.filename) &&
        Objects.equals(this.inputStream, resource.inputStream) &&
        Objects.equals(this.open, resource.open) &&
        Objects.equals(this.readable, resource.readable) &&
        Objects.equals(this.uri, resource.uri) &&
        Objects.equals(this.url, resource.url);
  }

  @Override
  public int hashCode() {
    return Objects.hash(description, file, filename, inputStream, open, readable, uri, url);
  }


  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class Resource {\n");

    sb.append("    description: ").append(toIndentedString(description)).append("\n");
    sb.append("    file: ").append(toIndentedString(file)).append("\n");
    sb.append("    filename: ").append(toIndentedString(filename)).append("\n");
    sb.append("    inputStream: ").append(toIndentedString(inputStream)).append("\n");
    sb.append("    open: ").append(toIndentedString(open)).append("\n");
    sb.append("    readable: ").append(toIndentedString(readable)).append("\n");
    sb.append("    uri: ").append(toIndentedString(uri)).append("\n");
    sb.append("    url: ").append(toIndentedString(url)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(java.lang.Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }

}

【问题讨论】:

  • 我猜你应该添加一个自定义的 httpmessageconverter。试试看这里docs.spring.io/spring/docs/current/spring-framework-reference/…
  • 你有没有检查this PR 是针对Spring的修复文件问题
  • @Angelo Immediata,感谢您的建议,虽然错误信息与io.swagger.client.model.Resource缺少HTTPMessageConverter直接相关,但真正的问题是我无法获取文件内容来自生成的io.swagger.client.model.Resource,因此无法将其转换为其他任何东西。
  • @Xin Meng,是的,我已经检查过了,但由于缺乏示例,我不明白。作者说他添加了一个命令行选项resourceStreams,但我尝试了java -jar swagger-codegen-cli.jar generate -i api-specs.json -o ./temp -l java --library resttemplate --resourceStreams,但得到了错误io.airlift.airline.ParseArgumentsUnexpectedException: Found unexpected parameters: [--resourceStreams]。对了,我用的是swagger codegner v2.3.1。
  • 我也试过java -jar swagger-codegen-cli.jar generate -i api-specs.json -o ./temp -l java --library resttemplate --additional-properties resourceStreams,运行成功但是io.swagger.client.model.Resource还是一样。

标签: java spring swagger swagger-codegen


【解决方案1】:

错误

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class io.swagger.client.model.Resource] and content type [image/jpeg] 

从线路发出

ResponseEntity<T> responseEntity = restTemplate.exchange(requestEntity, returnType);

在变体库resttemplate的Swagger Codegen自动生成的类io.swagger.client.ApiClient中,即相当于运行命令java -jar swagger-codegen-cli.jar generate -i api-specs.json -l java --library resttemplate

该错误表示resttemplate不知道如何将响应数据转换为io.swagger.client.model.Resource,并提示我们创建合适的HttpMessageConverterThis is a tutorial for creating a HttpMessageConverters and register them with the resttemplate.

然而,在我的情况下,HttpMessageConverter 不能成为解决方案,因为生成的io.swagger.client.model.Resource 没有属性来阻止 API 响应中的文件数据。 Swagger Codegen 似乎误解了 API 服务器上的控制器将返回 org.springframework.core.io.Resource 的 JSON 表示并因此生成计数器部分 io.swagger.client.model.Resource 用于在客户端接收 JSON 数据,但 API 服务器实际上以文件流响应数据,而不是 JSON。

我不知道这是否可能是 Swagger Codegen 的错误,或者我做错了什么。

尽管如此,我还是决定放弃在 API 服务器上使用 org.springframework.core.io.Resource。相反,我将控制器的返回类型更改为ResponseEntity&lt;byte[]&gt;,并手动将响应头配置为具有正确的内容类型和文件名,如下

资源控制器(API 服务器)

@GetMapping("/files/{uuid}")
@ResponseBody
public ResponseEntity<byte[]> getFile(@PathVariable String uuid) {
    FileMetadata myFileData = fileService.getFileMetadata(uuid);
    org.springframework.core.io.Resource res = new FileSystemResource(myFileData.getPaht());
    byte[] data = IOUtils.toByteArray(res.getInputStream());
    HttpHeaders respHeaders = new HttpHeaders();
    respHeaders.setContentType(MediaType.valueOf(myFileData.getContentType()));
    respHeaders.setContentLength(res.getFile().length());
    respHeaders.setContentDispositionFormData("attachment", myFileData.getName());
    return new ResponseEntity<>(data ,respHeaders, HttpStatus.OK);
}

然后,在 Web 客户端,我将从 API 服务器接收到的 byte[] 数据转换为 org.springframework.core.io.Resource,并通过 API 服务器的响应标头传递。

Web 客户端服务器上的控制器

@GetMapping("/client/files/{uuid}")
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> getFile(@PathVariable String uuid) {
    byte[] data = resourceControllerApi.getFileUsingGET(uuid);
    HttpHeaders responseHeader = (HttpHeaders) RequestContextHolder.getRequestAttributes().getAttribute("responseHeader", RequestAttributes.SCOPE_REQUEST);
    return new ResponseEntity<>(new ByteArrayResource(data), responseHeader, HttpStatus.OK);
}

关于我用来检索响应头的方式,由于我无法从生成的api类中获取响应头,所以我必须创建一个ClientHttpRequestInterceptor来获取头并将其作为请求属性。

public class ResponseHeaderClientRequestInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        ClientHttpResponse response = execution.execute(request, body);
        HttpHeaders responseHeader = response.getHeaders();
        RequestAttributes requestAttrs = RequestContextHolder.getRequestAttributes();
        if (requestAttrs != null) {
            requestAttrs.setAttribute("responseHeader", responseHeader, RequestAttributes.SCOPE_REQUEST);
        }
        return response;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 2019-04-09
    • 1970-01-01
    相关资源
    最近更新 更多