Swagger 文档将持续时间显示为
"duration": {
"seconds": 0,
"nano": 0,
"zero": true,
"negative": true,
"units": [
{
"dateBased": true,
"timeBased": true,
"durationEstimated": true
}
]
},
但实际格式是 ISO 8601 持续时间格式(PT0S),以下是代码段。有没有办法正确格式化文档?
任务文档
@Document(collection = "tasks")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
public class Task {
public enum Status {
todo, inprogress, done
}
@Id
private String id;
private String name;
private String description;
private Status status = Status.todo;
private Duration estimatedDuration = Duration.ZERO;
private Duration duration = Duration.ZERO;
}
初始化
@SpringBootApplication
@OpenAPIDefinition(info = @Info(title = "APIs v1.0.2", version = "1.0.2", description = "Documentation APIs v1.0.2"))
public class SBApplication {
......
}
pom依赖
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>1.4.3</version>
</dependency>
尝试
import io.swagger.v3.oas.annotations.media.Schema;
...
@Schema(type = "string", format = "duration")
private Duration estimatedDuration = Duration.ZERO;
@Schema(type = "string", format = "duration")
private Duration duration = Duration.ZERO;
在 swagger-core issues中有一些关于这个主题的讨论。
对我来说,窍门是使用一个大摇大摆的 ModelConverter :
@Component
public class DurationPropertyModelConverter implements ModelConverter {
@Override
public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
if (type.isSchemaProperty()) {
JavaType _type = Json.mapper().constructType(type.getType());
if (_type != null) {
Class<?> cls = _type.getRawClass();
if (Duration.class.isAssignableFrom(cls)) {
return new StringSchema().format("duration");
}
}
}
if (chain.hasNext()) {
return chain.next().resolve(type, context, chain);
} else {
return null;
}
}
}
代码采用 Kotlin 语言。
/**
* SwaggerDoc model converter for allowing basic schema inheritance.
*
* It achieves this by replacing known "problem" type schemas with the schema of a different type,
* similar to [SpringDocUtils.replaceWithSchema].
* Unlike the built-in schema replacement, [SchemaReplacer] correctly overrides type schema values
* with property schema values, rather than completely replacing one with the other.
*/
@Component
class SchemaReplacer : ModelConverter {
/**
* Which type schemas to replace.
*/
private val replacements = mutableMapOf<Class<*>, KClass<*>>()
/**
* Add a type schema replacement.
*/
fun replace(target: Class<*>, replacement: KClass<*>) {
replacements[target] = replacement
}
override fun resolve(
target: AnnotatedType,
context: ModelConverterContext,
chain: MutableIterator<ModelConverter>,
): Schema<*>? {
if (target.isSchemaProperty) {
replacements[(target.type as JavaType).rawClass]?.also { replacementType ->
val typeSchema = replacementType.findAnnotation<SchemaAnnotation>()
val propSchema = target.ctxAnnotations.filterIsInstance<SchemaAnnotation>().singleOrNull()
return Schema<Any>().apply {
type =
propSchema?.type.takeUnless { it.isNullOrBlank() }
?: typeSchema?.type.takeUnless { it.isNullOrBlank() }
?: StringSchema().type
format =
propSchema?.format.takeUnless { it.isNullOrBlank() }
?: typeSchema?.format.takeUnless { it.isNullOrBlank() }
title =
propSchema?.title.takeUnless { it.isNullOrBlank() }
?: typeSchema?.title.takeUnless { it.isNullOrBlank() }
description =
propSchema?.description.takeUnless { it.isNullOrBlank() }
?: typeSchema?.description.takeUnless { it.isNullOrBlank() }
pattern =
propSchema?.pattern.takeUnless { it.isNullOrBlank() }
?: typeSchema?.pattern.takeUnless { it.isNullOrBlank() }
example =
propSchema?.example.takeUnless { it.isNullOrBlank() }
?: typeSchema?.example.takeUnless { it.isNullOrBlank() }
// ...etc
}
}
}
if (chain.hasNext())
return chain.next().resolve(target, context, chain)
else
return null
}
}
要使用它,请设置一个替换类,然后设置一个属性:
@Schema(title = "Duration", format = "duration", example = "PT3H4M2S")
class DurationSchema
//---
@Configuration
class OpenApiConfig(replacer: SchemaReplacer) {
init {
replacer.replace(Duration::class.java, DurationSchema::class)
}
}
//---
data class DTO(
@field:Schema(title = "Total duration")
val totalDuration: Duration,
}
这将生成一个 OpenAPI 文档,其中 totalDuration
具有来自属性的标题以及来自
DurationSchema
类的所有其他架构属性。