Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] 集成openapi有误,参考#387 #1256

Closed
towangle opened this issue Mar 21, 2023 · 8 comments
Closed

[BUG] 集成openapi有误,参考#387 #1256

towangle opened this issue Mar 21, 2023 · 8 comments
Labels
bug Something isn't working fixed
Milestone

Comments

@towangle
Copy link

问题描述

简要描述您碰到的问题。
springboot3.0.4 ,openapi 2.0.4, 无法正常使用
类似原问题
#387

环境信息

请填写以下信息:

  • OS信息: [e.g.:win10 ]
  • JDK信息: [e.g.:Openjdk 17]
  • 版本信息:[e.g.:Fastjson2 2.0.26]

重现步骤

如何操作可以重现该问题:

1.使用jackson作为消息转换器,正常使用:
image

2.使用fastjson2 作为消息转换器,接口返回异常:
image
image
image

返回header无Content-Length 。手工设置无用。

该版本内,无任何有关openapi的处理,
image

https://github.com/alibaba/fastjson2/blob/32a4f418208a859dea324e2bac6570bad9f6ce3b/extension-spring5/src/test/java/com/alibaba/fastjson2/springdoc/OpenApiJsonWriterTest.java
测试类没看懂
image
#387 修复没看懂

期待的正确结果

对您期望发生的结果进行清晰简洁的描述。
#387 说2.0.6修复,现在版本2.0.26,期望处理。

相关日志输出

请复制并粘贴任何相关的日志输出。

附加信息

如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。

@towangle towangle added the bug Something isn't working label Mar 21, 2023
@towangle towangle changed the title [BUG] [BUG] 集成openapi有误,参考#387 Mar 21, 2023
@wenshao
Copy link
Member

wenshao commented Mar 22, 2023

@towangle 我不是很熟悉Swagger,需要些时间学习才能处理

@YangLiCcc
Copy link

YangLiCcc commented Mar 24, 2023

@towangle 我不是很熟悉Swagger,需要些时间学习才能处理

@wenshao

这个问题类似 #387,是同时使用 FastJsonHttpMessageConverter 和 springdoc-openapi 出现的。

springdoc 需要的好像是 String 类型,但是被FastJsonHttpMessageConverter特殊处理之后就不行了...

提供一下我使用的依赖:

image

image

MessageConverter:

image

同样,最后访问 springdoc 是如下错误:
image

@Merely-chao
Copy link

Merely-chao commented Mar 27, 2023

@towangle 可以试试
` //converters.add(0,converter);

    converters.add(new ByteArrayHttpMessageConverter());

    converters.add(converter);`

@OnceCrazyer
Copy link

有解决吗?

@telechow
Copy link

@towangle @wenshao
这个问题,我昨天也遇到了。说一下我的解决方法及思路,希望是正确的。

我的版本是

<properties>
        <java.version>17</java.version>
		<spring-boot.version>3.0.4</spring-boot.version>
		<!--fastjson2-->
		<fastjson2.version>2.0.26</fastjson2.version>
		<!--openapi-->
		<springdoc-openapi-starter-webmvc-ui.version>2.0.4</springdoc-openapi-starter-webmvc-ui.version>
</properties>


<dependencies>
			<!--fastjson2-->
			<dependency>
				<groupId>com.alibaba.fastjson2</groupId>
				<artifactId>fastjson2-extension-spring6</artifactId>
				<version>${fastjson2.version}</version>
			</dependency>

			<!--接口文档-->
			<dependency>
				<groupId>org.springdoc</groupId>
				<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
				<version>${springdoc-openapi-starter-webmvc-ui.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springdoc</groupId>
				<artifactId>springdoc-openapi-starter-common</artifactId>
				<version>${springdoc-openapi-starter-webmvc-ui.version}</version>
			</dependency>
</dependencies>

和你的一样,都是最新的。

然后按照fastjson2的推荐配置

@Configuration
@EnableWebMvc
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        //自定义配置...
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        config.setReaderFeatures(JSONReader.Feature.FieldBased, JSONReader.Feature.SupportArrayToBean);
        config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue, JSONWriter.Feature.PrettyFormat);
        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(StandardCharsets.UTF_8);
        converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
        converters.add(0, converter);
    }
}

注意这里有一个@EnableWebMvc注解,有没有这个注解,很不一样。

  • 当有这个注解时,converters将会被覆盖,执行完此方法之后只有FastJsonHttpMessageConverter这一个转换器。

  • 当没有这个注解时,我们打个断点看下,有很多个转换器

    2023-03-31-11-18-49-image

    FastJsonHttpMessageConverter被放在最前面,如果fastjson的转换器能够处理就会先处理了。

接下来,我们找到openapiapi-docscontrollerorg.springdoc.webmvc.api.OpenApiWebMvcResource

2023-03-31-11-21-25-image

它的响应是一个字节数组,且强制指定了MediaType。

然后我们再看看FastJsonHttpMessageConverter的源码的关键方法。

2023-03-31-11-23-54-image

可以看到并没有对字节数组类型的响应做特殊处理,直接JSON.writeTo写到HttpOutputMessagebody中了。

那么,我现在想到有两种解法。

  1. 配置的时候,不要加上@EnableWebMvc注解,并且将FastJsonHttpMessageConverter放在ByteArrayHttpMessageConverter之后

    对响应的类型为byte[]的控制器,先交给ByteArrayHttpMessageConverter处理,那么FastJsonHttpMessageConverter就不会去处理了。

    //@EnableWebMvc
    @Configuration
    @RequiredArgsConstructor(onConstructor_ = @Autowired)
    public class Fastjson2WebMvcConfigurer implements WebMvcConfigurer {
    
        private final FastJsonConfig fastJsonConfig;
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
            converter.setFastJsonConfig(fastJsonConfig);
            //2.设置字符集为UTF8
            converter.setDefaultCharset(StandardCharsets.UTF_8);
            //3.设置支持的MediaType为application/json
            converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
            converters.add(1, converter);
        }
    
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            FastJsonJsonView fastJsonJsonView = new FastJsonJsonView();
            //1.使用io.github.telechow.garoupa.config.fastjson2.Fastjson2Configuration中的配置
            fastJsonJsonView.setFastJsonConfig(fastJsonConfig);
            registry.enableContentNegotiation(fastJsonJsonView);
        }
    }

    这种方法的缺点是,converters.add(1, converter);这句里面有个魔法值,而且我也不确定ByteArrayHttpMessageConverter会不会每次都在converters最前面。

  1. @EnableWebMvc注解随便有没有,继承FastJsonHttpMessageConverter,并重写writeInternal方法,仅仅对byte[]类型做了一下处理

    public class CustomFastJsonHttpMessageConverter extends FastJsonHttpMessageConverter {
    
        private FastJsonConfig config = new FastJsonConfig();
    
        /**
         * @return the fastJsonConfig.
         */
        public FastJsonConfig getFastJsonConfig() {
            return config;
        }
    
        /**
         * @param fastJsonConfig the fastJsonConfig to set.
         */
        public void setFastJsonConfig(FastJsonConfig fastJsonConfig) {
            this.config = fastJsonConfig;
        }
    
        @Override
        protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            HttpHeaders headers = outputMessage.getHeaders();
    
            try {
                int contentLength;
    
                //我用的jdk17,这里是instanceof模式匹配语法,jdk8没有的,自己强转一下object
                if(object instanceof byte[] bytes){
                    contentLength = bytes.length;
                    outputMessage.getBody().write(bytes, 0, bytes.length);
                }
                else if (object instanceof String && JSON.isValidObject((String) object)) {
                    byte[] strBytes = ((String) object).getBytes(config.getCharset());
                    contentLength = strBytes.length;
                    outputMessage.getBody().write(strBytes, 0, strBytes.length);
                } else {
                    contentLength = JSON.writeTo(
                            outputMessage.getBody(),
                            object, config.getDateFormat(),
                            config.getWriterFilters(),
                            config.getWriterFeatures()
                    );
                }
    
                if (headers.getContentLength() < 0 && config.isWriteContentLength()) {
                    headers.setContentLength(contentLength);
                }
            } catch (JSONException ex) {
                throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
            } catch (IOException ex) {
                throw new HttpMessageNotWritableException("I/O error while writing output message", ex);
            }
        }
    }

    然后,配置文件

        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            CustomFastJsonHttpMessageConverter converter = new CustomFastJsonHttpMessageConverter();
            converter.setFastJsonConfig(fastJsonConfig);
            //2.设置字符集为UTF8
            converter.setDefaultCharset(StandardCharsets.UTF_8);
            //3.设置支持的MediaType为application/json
            converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
            converters.add(0, converter);
        }

我们还可以再多思考一些,fastjson2是不是对所有的byte[]类型的响应处理起来都有问题呢?

一般情况下,我们是不会像openapi这样响应一个byte[],又强制指定MeidaTypeapplication/json的。这种写法,比较少见。

2023-03-31-11-53-23-image

  1. 我们保持fastjson2的推荐配置,并写个简单的controller试试看
@Configuration
@EnableWebMvc
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        //自定义配置...
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        config.setReaderFeatures(JSONReader.Feature.FieldBased, JSONReader.Feature.SupportArrayToBean);
        config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue, JSONWriter.Feature.PrettyFormat);
        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(StandardCharsets.UTF_8);
        converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
        converters.add(0, converter);
    }
}
/**
 * 首页 控制器
 *
 * @author Telechow
 * @since 2023/1/30 15:24
 */
@RestController
@RequestMapping("/index")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class IndexController {

	private final IIndexService indexService;

	/**
	 * 展示首页
	 *
	 * @param text 前端说的话
	 * @return byte[] 首页的一句话
	 * @author Telechow
	 * @since 2023/1/30 15:27
	 */
	@GetMapping(value = "/show")
//	@GetMapping(value = "/show", produces = MediaType.APPLICATION_JSON_VALUE)
	public byte[] show(@RequestParam(name = "text") String text) {
		String show = indexService.show(text);
		return show.getBytes(StandardCharsets.UTF_8);
	}
}

结果是不论我们@GetMapping是否强制指定MediaType,响应都不符合预期。

2023-03-31-12-11-30-image

因为@RestController会把MediaType设置为application/json,此控制器的byte[]响应会被FastJsonHttpMessageConverter处理

  1. 我们把@RestController换成@Controller@GetMapping不指定MediaType

    结果是会抛异常

    2023-03-31-11-51-56-image

因为FastJsonHttpMessageConverter配置了converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));只支持application/json,而@Controller@GetMapping都没有将MediaType设置为application/json

且使用了@EnableWebMvc导致,转换器只有一个,没有任何转换器能够处理,所以抛异常了。

  1. 我们使用我的两种解法中的任意一种

    发现可以正常得到结果。因为我们可以在MediaTypeapplication/json的情况下,去处理byte[]类型的响应了。


最后推销一下自己,辞职一个月了,最近在找工作。等一个有缘人内推。我的邮箱laughho@qq.com

@TsukasaHwan
Copy link

@towangle @wenshao 这个问题,我昨天也遇到了。说一下我的解决方法及思路,希望是正确的。

我的版本是

<properties>
        <java.version>17</java.version>
		<spring-boot.version>3.0.4</spring-boot.version>
		<!--fastjson2-->
		<fastjson2.version>2.0.26</fastjson2.version>
		<!--openapi-->
		<springdoc-openapi-starter-webmvc-ui.version>2.0.4</springdoc-openapi-starter-webmvc-ui.version>
</properties>


<dependencies>
			<!--fastjson2-->
			<dependency>
				<groupId>com.alibaba.fastjson2</groupId>
				<artifactId>fastjson2-extension-spring6</artifactId>
				<version>${fastjson2.version}</version>
			</dependency>

			<!--接口文档-->
			<dependency>
				<groupId>org.springdoc</groupId>
				<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
				<version>${springdoc-openapi-starter-webmvc-ui.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springdoc</groupId>
				<artifactId>springdoc-openapi-starter-common</artifactId>
				<version>${springdoc-openapi-starter-webmvc-ui.version}</version>
			</dependency>
</dependencies>

和你的一样,都是最新的。

然后按照fastjson2的推荐配置

@Configuration
@EnableWebMvc
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        //自定义配置...
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        config.setReaderFeatures(JSONReader.Feature.FieldBased, JSONReader.Feature.SupportArrayToBean);
        config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue, JSONWriter.Feature.PrettyFormat);
        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(StandardCharsets.UTF_8);
        converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
        converters.add(0, converter);
    }
}

注意这里有一个@EnableWebMvc注解,有没有这个注解,很不一样。

  • 当有这个注解时,converters将会被覆盖,执行完此方法之后只有FastJsonHttpMessageConverter这一个转换器。
  • 当没有这个注解时,我们打个断点看下,有很多个转换器
    2023-03-31-11-18-49-image
    FastJsonHttpMessageConverter被放在最前面,如果fastjson的转换器能够处理就会先处理了。

接下来,我们找到openapiapi-docscontrollerorg.springdoc.webmvc.api.OpenApiWebMvcResource

2023-03-31-11-21-25-image

它的响应是一个字节数组,且强制指定了MediaType。

然后我们再看看FastJsonHttpMessageConverter的源码的关键方法。

2023-03-31-11-23-54-image

可以看到并没有对字节数组类型的响应做特殊处理,直接JSON.writeTo写到HttpOutputMessagebody中了。

那么,我现在想到有两种解法。

  1. 配置的时候,不要加上@EnableWebMvc注解,并且将FastJsonHttpMessageConverter放在ByteArrayHttpMessageConverter之后
    对响应的类型为byte[]的控制器,先交给ByteArrayHttpMessageConverter处理,那么FastJsonHttpMessageConverter就不会去处理了。
    //@EnableWebMvc
    @Configuration
    @RequiredArgsConstructor(onConstructor_ = @Autowired)
    public class Fastjson2WebMvcConfigurer implements WebMvcConfigurer {
    
        private final FastJsonConfig fastJsonConfig;
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
            converter.setFastJsonConfig(fastJsonConfig);
            //2.设置字符集为UTF8
            converter.setDefaultCharset(StandardCharsets.UTF_8);
            //3.设置支持的MediaType为application/json
            converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
            converters.add(1, converter);
        }
    
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            FastJsonJsonView fastJsonJsonView = new FastJsonJsonView();
            //1.使用io.github.telechow.garoupa.config.fastjson2.Fastjson2Configuration中的配置
            fastJsonJsonView.setFastJsonConfig(fastJsonConfig);
            registry.enableContentNegotiation(fastJsonJsonView);
        }
    }

    这种方法的缺点是,converters.add(1, converter);这句里面有个魔法值,而且我也不确定ByteArrayHttpMessageConverter会不会每次都在converters最前面。

  1. @EnableWebMvc注解随便有没有,继承FastJsonHttpMessageConverter,并重写writeInternal方法,仅仅对byte[]类型做了一下处理

    public class CustomFastJsonHttpMessageConverter extends FastJsonHttpMessageConverter {
    
        private FastJsonConfig config = new FastJsonConfig();
    
        /**
         * @return the fastJsonConfig.
         */
        public FastJsonConfig getFastJsonConfig() {
            return config;
        }
    
        /**
         * @param fastJsonConfig the fastJsonConfig to set.
         */
        public void setFastJsonConfig(FastJsonConfig fastJsonConfig) {
            this.config = fastJsonConfig;
        }
    
        @Override
        protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            HttpHeaders headers = outputMessage.getHeaders();
    
            try {
                int contentLength;
    
                //我用的jdk17,这里是instanceof模式匹配语法,jdk8没有的,自己强转一下object
                if(object instanceof byte[] bytes){
                    contentLength = bytes.length;
                    outputMessage.getBody().write(bytes, 0, bytes.length);
                }
                else if (object instanceof String && JSON.isValidObject((String) object)) {
                    byte[] strBytes = ((String) object).getBytes(config.getCharset());
                    contentLength = strBytes.length;
                    outputMessage.getBody().write(strBytes, 0, strBytes.length);
                } else {
                    contentLength = JSON.writeTo(
                            outputMessage.getBody(),
                            object, config.getDateFormat(),
                            config.getWriterFilters(),
                            config.getWriterFeatures()
                    );
                }
    
                if (headers.getContentLength() < 0 && config.isWriteContentLength()) {
                    headers.setContentLength(contentLength);
                }
            } catch (JSONException ex) {
                throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
            } catch (IOException ex) {
                throw new HttpMessageNotWritableException("I/O error while writing output message", ex);
            }
        }
    }

    然后,配置文件

        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            CustomFastJsonHttpMessageConverter converter = new CustomFastJsonHttpMessageConverter();
            converter.setFastJsonConfig(fastJsonConfig);
            //2.设置字符集为UTF8
            converter.setDefaultCharset(StandardCharsets.UTF_8);
            //3.设置支持的MediaType为application/json
            converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
            converters.add(0, converter);
        }

我们还可以再多思考一些,fastjson2是不是对所有的byte[]类型的响应处理起来都有问题呢?

一般情况下,我们是不会像openapi这样响应一个byte[],又强制指定MeidaTypeapplication/json的。这种写法,比较少见。

2023-03-31-11-53-23-image

  1. 我们保持fastjson2的推荐配置,并写个简单的controller试试看
@Configuration
@EnableWebMvc
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        //自定义配置...
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        config.setReaderFeatures(JSONReader.Feature.FieldBased, JSONReader.Feature.SupportArrayToBean);
        config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue, JSONWriter.Feature.PrettyFormat);
        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(StandardCharsets.UTF_8);
        converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
        converters.add(0, converter);
    }
}
/**
 * 首页 控制器
 *
 * @author Telechow
 * @since 2023/1/30 15:24
 */
@RestController
@RequestMapping("/index")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class IndexController {

	private final IIndexService indexService;

	/**
	 * 展示首页
	 *
	 * @param text 前端说的话
	 * @return byte[] 首页的一句话
	 * @author Telechow
	 * @since 2023/1/30 15:27
	 */
	@GetMapping(value = "/show")
//	@GetMapping(value = "/show", produces = MediaType.APPLICATION_JSON_VALUE)
	public byte[] show(@RequestParam(name = "text") String text) {
		String show = indexService.show(text);
		return show.getBytes(StandardCharsets.UTF_8);
	}
}

结果是不论我们@GetMapping是否强制指定MediaType,响应都不符合预期。

2023-03-31-12-11-30-image

因为@RestController会把MediaType设置为application/json,此控制器的byte[]响应会被FastJsonHttpMessageConverter处理

  1. 我们把@RestController换成@Controller@GetMapping不指定MediaType
    结果是会抛异常
    2023-03-31-11-51-56-image

因为FastJsonHttpMessageConverter配置了converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));只支持application/json,而@Controller@GetMapping都没有将MediaType设置为application/json

且使用了@EnableWebMvc导致,转换器只有一个,没有任何转换器能够处理,所以抛异常了。

  1. 我们使用我的两种解法中的任意一种
    发现可以正常得到结果。因为我们可以在MediaTypeapplication/json的情况下,去处理byte[]类型的响应了。

最后推销一下自己,辞职一个月了,最近在找工作。等一个有缘人内推。我的邮箱laughho@qq.com

点个赞,今天刚好遇到这个问题,跟我想的一样

@wenshao
Copy link
Member

wenshao commented Apr 2, 2023

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.27-SNAPSHOT/
感谢认真诊断反馈问题,问题已修复,请帮忙用2.0.27-SNAPSHOT版本验证

@wenshao wenshao added this to the 2.0.27 milestone Apr 2, 2023
@wenshao wenshao added the fixed label Apr 2, 2023
@wenshao
Copy link
Member

wenshao commented Apr 8, 2023

https://github.com/alibaba/fastjson2/releases/tag/2.0.27
问题已修复,请用新版本

@wenshao wenshao closed this as completed Apr 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed
Projects
None yet
Development

No branches or pull requests

7 participants