plugins {
...
id("org.jetbrains.dokka") version "1.6.10" apply false
...
}
dependencies {
....
implementation("org.springdoc:springdoc-openapi-ui:[version]")
implementation("org.springdoc:springdoc-openapi-kotlin:[version]")
....
}
data class TaskDto(
@Schema(nullable = true)val id: Long? = null,
val title: String,
@Schema(description = "Идентификатор из таблицы [Project](<#/ProjectController/findAll>)")val projectId: Long,
@Schema(description = "Идентификатор из таблицы [UserProfile](<#/UserProfileController/findUserProfiles>) проставляется системой при создании в дальнейших изменениях должен быть передан",
nullable = true
)val creatorId: Long? = null,
@Schema(description = "Идентификатор из таблицы [TaskStatusRef](<#/TaskStatusRefController/findAllTaskStatusRef>)")val statusId: Long,
@Schema(description = "Модификатор статуса (TaskStatusModifierRef.id) 1 - Сделать, 2 - Сделано")val statusModifierId: Long,
@Schema(description = "Идентификатор из таблицы [TaskTypeRef](<#/TaskTypeRefController/findAllTaskTypes>)")
....
@Configuration
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
class SwaggerConfig {
}
// В контроллерах:
@Operation(summary = "create comment",
description = "Создание комментария",
security = [SecurityRequirement(name = "bearerAuth")]
)
@PostMapping
fun ....
task("dbdoc") {
doLast {
for (schema in listOf("перечень", "схем", "для", "документации")) {
for (format in listOf("htmlx", "html")) {
exec {
workingDir("$projectDir/../schemacrawler/bin")
val prefix = if (format == "htmlx") "g_" else ""
commandLine(listOf("$projectDir/../jaga-db/schemacrwl/schemacrwl.cmd",
schema, format, "$buildDir/$prefix$schema.html", jagaDBPassw))
}
}
}
}
}
@Bean
fun v1Api(): GroupedOpenApi? {
return GroupedOpenApi.builder()
.group("v1")
.pathsToMatch("/v1/**")
.build()
}
@Bean
fun v4Api(): GroupedOpenApi? {
return GroupedOpenApi.builder()
.group("v4")
.pathsToMatch("/v{4}/**")
.build()
}
@Bean
fun v2Api(): GroupedOpenApi? {
return GroupedOpenApi.builder()
.group("v2")
.pathsToMatch("/v2/**")
.build()
}
object API {
const val ANY = "v{v:1|2}" // любая версия
const val TILL1 = "v{v:1}" // любая версия ДО указанной
const val FROM_V2 = "v{v:2}" // версия, начинающаяся С указанной
}
...
@PostMapping("/${API.ANY}/project/{projectId}/generatePutObjectUrl")
...
@Bean
fun v1Api(): GroupedOpenApi? {
return GroupedOpenApi.builder()
.group("v1")
.addOpenApiMethodFilter(Filter("v1"))
.build()
}
@Bean
fun v2Api(): GroupedOpenApi? {
return GroupedOpenApi.builder()
.group("v2")
.addOpenApiMethodFilter(Filter("v2"))
.build()
}
class Filter(val version: String) : OpenApiMethodFilter {
override fun isMethodToInclude(method: Method): Boolean {
return if (
method.getAnnotation(GetMapping::class.java)?.value?.any { match(version, it) } ?: false
|| method.getAnnotation(PostMapping::class.java)?.value?.any { match(version, it) } ?: false
|| method.getAnnotation(PutMapping::class.java)?.value?.any { match(version, it) } ?: false
|| method.getAnnotation(DeleteMapping::class.java)?.value?.any { match(version, it) } ?: false
) {
true
} else {
false
}
}
fun match(target: String, path: String): Boolean {
val splited = path.split("/")
val firstToken = splited.getOrNull(1)
firstToken?.let {
val matcher = AntPathMatcher()
return matcher.match(firstToken, target)
}
return false
}
@Query(
"""select * from jgproj_api.f_search_project_by_name(
p_search_text => cast(:search_text as text),
p_user_id => cast(cast(:user_id as text) as bigint),
p_offset=> :offset,
p_limit=> :limit,
p_sort=> cast(:sort as text))""",
nativeQuery = true
)
...
// описание атрибута в модели
@JsonProperty("user_project_authorities")
@Type(type = "com.rit.crossdev.jaga.util.StringArrayUserType")
@Column(name = "user_project_authorities")
val userProjectAuthorities: Array<String>?
...
// нюанс описания null - не null для самого массива и для элементов массива
@ArraySchema(
schema = Schema(description = "Роли пользователей", nullable = false),
arraySchema = Schema(description = "Роли пользователей", required = false, nullable = true)
)
val userProjectRoles: Array<String>? = null
...
package com.rit.crossdev.jaga.util
import org.hibernate.HibernateException
import org.hibernate.engine.spi.SharedSessionContractImplementor
import org.hibernate.usertype.UserType
import java.io.Serializable
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Types
class StringArrayUserType : UserType {
private val typeParameterClass: Class<Array<String?>>? = null
override fun assemble(cached: Serializable, owner: Any): Any? {
return deepCopy(cached)
}
override fun deepCopy(value: Any?): Any? {
return value
}
override fun disassemble(value: Any?): Array<String>? {
return deepCopy(value) as Array<String>?
}
@Throws(HibernateException::class)
override fun equals(x: Any?, y: Any?): Boolean {
return if (x == null) {
y == null
} else x == y
}
override fun hashCode(x: Any): Int {
return x.hashCode()
}
override fun nullSafeGet(
resultSet: ResultSet,
names: Array<String>,
sharedSessionContractImplementor: SharedSessionContractImplementor,
o: Any
): Array<String>? {
val array = resultSet.getArray(names[0])
return if (array != null) {
array.array as Array<String>
} else {
arrayOf<String>()
}
}
override fun nullSafeSet(
statement: PreparedStatement,
value: Any?,
index: Int,
sharedSessionContractImplementor: SharedSessionContractImplementor
) {
val connection = statement.connection
if (value == null) {
statement.setNull(index, SQL_TYPES[0])
} else {
val castObject = value as Array<String>?
val array = connection.createArrayOf("integer", castObject)
statement.setArray(index, array)
}
}
override fun isMutable(): Boolean {
return true
}
override fun replace(original: Any, target: Any, owner: Any): Any {
return original
}
override fun returnedClass(): Class<Array<String?>> {
return typeParameterClass!!
}
override fun sqlTypes(): IntArray {
return intArrayOf(Types.ARRAY)
}
companion object {
protected val SQL_TYPES = intArrayOf(Types.ARRAY)
}
}