我在我的OpenApi项目中使用SpringBoot 3来生成Swagger页面。
POM.xml中的依赖项:
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> <version>1.5.12</version> </dependency>
在Controller类中,我在方法上面定义了以下注释。
@Operation(
summary = "Get a list of letters for a specific user",
description = "Get a list of letters for a specific user",
tags = {"letters"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "success", content = {@Content(
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = LetterDTO.class)))}),
@ApiResponse(responseCode = "400", description = "BAD REQUEST"),
@ApiResponse(responseCode = "401", description = "UNAUTHORIZED"),
@ApiResponse(responseCode = "403", description = "Forbidden"),
@ApiResponse(responseCode = "404", description = "NOT_FOUND: Entity could not be found")}
)
@GetMapping(value = "letters/user/{userId}", produces = {"application/json"})
public List<LetterDTO> getLettersForUser(
...
)
Swagger的输出显示了代码200的正确响应,这是一个LetterDTO对象列表。
但是代码401的响应也显示了一个LetterDTO对象的列表。我没有为代码401定义任何响应对象。我期望Swagger生成与代码400相同的响应对象,这是一个包含错误代码和错误消息的默认返回对象。
为什么Swagger使用与为代码200定义的返回对象相同的返回对象?我原以为Swagger会生成默认的返回对象。这是斯威格的臭虫吗?
发布于 2022-04-17 08:21:01
我通常会像这样配置API响应:
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "400", description = "Invalid request", content = @Content)
如果未指定content
,则使用相应控制器方法的返回类型。content = @Content
告诉Swagger,响应中没有内容。
对于@ApiGetOne
,Swagger将显示(屏幕截图来自不同的DTO类):
为了简单性和可重用性,我通常用可重用的助手注释包装这些注释,这样我的端点就没有那么多的注释,并且我不需要控制器中的ResponseEntity
,例如:
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "400", description = "Invalid request", content = @Content)
@ApiResponse(responseCode = "500", description = "Internal error", content = @Content)
public @interface ApiGet {
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
}
您还可以使用更多的API响应来扩展这些注释,例如,为某些端点添加404,创建另一个具有@ApiGet
的注释:
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ApiGet
@ApiResponse(responseCode = "404", description = "Not found", content = @Content)
public @interface ApiGetOne {
@AliasFor(annotation = ApiGet.class)
String[] value() default {};
@AliasFor(annotation = ApiGet.class)
String[] path() default {};
}
最后,在任何端点上使用它们(使用Java 17):
public record HelloWorldDto(String recipientName) {
public String getMessage() {
return "Hello, %s".formatted(recipientName);
}
}
public record ErrorDto(String message) {
}
@RestController
@RequestMapping("api/test")
@Tag(name = "Demo", description = "Endpoints for testing")
public class DemoController {
...
@ApiGet("/hello")
public HelloWorldDto sayHello() {
return new HelloWorldDto("stranger");
}
@ApiGetOne("/hello/{id}")
public HelloWorldDto sayHelloWithParam(@PathVariable int id) {
final var person = myPersonRepo.getById(id); // might throw a NotFoundException which is mapped to 404 status code
return new HelloWorldDto(person.name());
}
}
将异常映射到自定义错误响应:
@ControllerAdvice
public class ErrorHandler {
private static final Logger log = LoggerFactory.getLogger(ErrorHandler.class);
@ExceptionHandler
public ResponseEntity<ErrorDto> handle(Exception exception) {
log.error("Internal server error occurred", exception);
return response(HttpStatus.INTERNAL_SERVER_ERROR, "Unknown error occurred.");
}
@ExceptionHandler
public ResponseEntity<ErrorDto> handle(NotFoundException exception) {
return response(HttpStatus.NOT_FOUND, exception.getMessage());
}
private ResponseEntity<ErrorDto> response(HttpStatus status, String message) {
return ResponseEntity
.status(status)
.body(new ErrorDto(message));
}
}
我很喜欢这个装置,因为
ResponseEntity
-- @ControllerAdvice
作为可重用错误处理H 122
的中心点,所有这些都使我的控制器/端点保持干净和简单的H 223H 124,然后继续测试简单的H 225/code>F 226
更新2022/04/20
只需修复一个bug,在那里我们有一个返回图像而不是JSON的端点。在这种情况下,为了防止HttpMessageNotWritableException: No converter for [class ErrorDto] with preset Content-Type 'image/jpeg',您需要像这样检查请求的Accept头(使用报头作为后盾):
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDto> handle(final Exception exception, final WebRequest webRequest) {
return createResponse(HttpStatus.INTERNAL_SERVER_ERROR, "Some error", webRequest);
}
protected ResponseEntity<ErrorDto> createResponse(final HttpStatus httpStatus,
final String message,
final WebRequest webRequest) {
final var accepts = webRequest.getHeader(HttpHeaders.ACCEPT);
if (!MediaType.APPLICATION_JSON_VALUE.equals(accepts)) {
return ResponseEntity.status(httpStatus)
.header("my-error", message)
.build();
}
return ResponseEntity
.status(status)
.body(new ErrorDto(message));
}
发布于 2021-11-11 11:11:32
这是一个不断出现的问题,因为Java方法只能具有返回类型。您的方法有一个响应类型的List<LetterDTO>
,所以无论您返回的HTTP,响应都是该结构的。
为了解决这一问题,大多数人采取以下办法之一:
Object
的返回类型,实际上已重定向。在这种情况下,用户必须依赖于API文档来处理响应,correctly.@RequestMapping()
选择器使用优化的选择,而不仅仅是端点路径。例如,也使用请求方法。但是,这样做的缺点是定义了新的REST端点。但总的来说,没有一条容易的出路。
编辑:添加示例
让我们假设这些简单的对象作为数据结构。
@NoArgsConstructor @AllArgsConstructor
@Getter @Setter
private static class LetterDTO{
String from;
}
@NoArgsConstructor @AllArgsConstructor
@Getter @Setter
private static class ErrorResp{
String message;
}
然后,对于第一个使用Object
作为返回值的情况,可以执行如下操作:
@GetMapping(value = "letters/user/{userId}", produces = {"application/json"})
public Object getLettersForUser( @PathVariable( "userId") String input, HttpServletResponse resp ) {
if( input.equalsIgnoreCase( "a" ) ) {
resp.setStatus( 200 );
return Arrays.asList( new LetterDTO( "A Friend" ) );
}
else{
resp.setStatus( 415 ); //Invalid user
return new ErrorResp( "Invalid user" );
}
}
或者更好地使用SpringMVC方式,即返回ResponseEntity
@GetMapping(value = "letters2/user/{userId}", produces = {"application/json"})
public ResponseEntity<Object> getLettersForUser2( @PathVariable( "userId") String input, HttpServletResponse resp ) {
if( input.equalsIgnoreCase( "a" ) ) {
return ResponseEntity.ok().body( Arrays.asList( new LetterDTO( "A Friend" ) ) );
}
else{
return ResponseEntity.status( 415 ).body( Arrays.asList( new ErrorResp( "Invalid user" ) ) );
}
}
现在必须清楚地将类用作返回值。与Object
不同,您可以作为包装器返回一些结构,例如:
@NoArgsConstructor @AllArgsConstructor
@Getter @Setter
private static class APIResponse{
String message;
Object data;
}
从这些例子中可以看出,API的文档变得非常重要,因为很难从签名中收集响应的内容-- 200、415等。
https://stackoverflow.com/questions/69927033
复制相似问题