refactor(domain): 参数获取修改

This commit is contained in:
AiKrai 2025-03-12 17:56:45 +08:00
parent 278c5eb810
commit fa18c92a02
32 changed files with 343 additions and 143 deletions

View File

@ -17,6 +17,7 @@ object Application {
Config.init(vertx)
val getIt = InjectConfig.configure(vertx)
val mainVerticle = getIt.getInstance(MainVerticle::class.java)
vertx.deployVerticle(mainVerticle).onComplete {
if (it.failed()) {
logger.error { "MainVerticle startup failed: ${it.cause()?.stackTraceToString()}" }

View File

@ -1,6 +1,6 @@
package app.config.auth
import app.domain.account.AccountRepository
import app.data.domain.account.AccountRepository
import app.port.reids.RedisClient
import cn.hutool.core.lang.Snowflake
import cn.hutool.core.util.IdUtil

View File

@ -1,6 +1,6 @@
package app.controller
import app.domain.account.LoginDTO
import app.data.domain.account.LoginDTO
import app.service.account.AccountService
import com.google.inject.Inject
import io.vertx.ext.web.RoutingContext

View File

@ -1,8 +1,8 @@
package app.controller
import app.domain.CargoType
import app.domain.account.Account
import app.domain.account.AccountRepository
import app.data.domain.account.Account
import app.data.domain.account.AccountRepository
import app.data.emun.Status
import app.service.account.AccountService
import com.google.inject.Inject
import mu.KotlinLogging
@ -28,13 +28,15 @@ class Demo1Controller @Inject constructor(
@D("name", "姓名") name: String?,
@D("age", "年龄") age: Int?,
@D("list", "列表") list: List<String>?,
@D("cargoType", "货物类型") cargoType: CargoType?
@D("status", "状态-0正常,1禁用,2删除") status: Status?,
@D("account", "账号") account: Account?
) {
logger.info { "你好" }
println(age)
println(list)
println("test-$name")
println(cargoType)
println(status)
println(account)
}
@D("事务测试")

View File

@ -1,6 +1,8 @@
package app.domain.account
package app.data.domain.account
import app.data.emun.Status
import org.aikrai.vertx.db.annotation.*
import org.aikrai.vertx.jackson.JsonUtil
import org.aikrai.vertx.utlis.BaseEntity
import java.sql.Timestamp
@ -23,7 +25,7 @@ class Account : BaseEntity() {
var password: String? = null
var status: Char? = null
var status: Status? = Status.ACTIVE
var delFlag: Char? = null
@ -31,4 +33,8 @@ class Account : BaseEntity() {
@TableField(fill = FieldFill.UPDATE)
var loginDate: Timestamp? = null
override fun toString(): String {
return JsonUtil.toJsonStr(this)
}
}

View File

@ -1,7 +1,7 @@
package app.domain.account
package app.data.domain.account
import app.base.domain.auth.modle.AccountRoleDTO
import app.domain.account.modle.AccountRoleAccessDTO
import app.data.domain.account.modle.AccountRoleAccessDTO
import app.data.domain.account.modle.AccountRoleDTO
import com.google.inject.ImplementedBy
import org.aikrai.vertx.db.Repository

View File

@ -1,7 +1,7 @@
package app.domain.account
package app.data.domain.account
import app.base.domain.auth.modle.AccountRoleDTO
import app.domain.account.modle.AccountRoleAccessDTO
import app.data.domain.account.modle.AccountRoleAccessDTO
import app.data.domain.account.modle.AccountRoleDTO
import com.google.inject.Inject
import io.vertx.sqlclient.SqlClient
import org.aikrai.vertx.db.RepositoryImpl

View File

@ -1,4 +1,4 @@
package app.domain.account
package app.data.domain.account
data class LoginDTO(
var username: String,

View File

@ -1,8 +1,8 @@
package app.domain.account.modle
package app.data.domain.account.modle
import app.domain.account.Account
import app.domain.menu.Menu
import app.domain.role.Role
import app.data.domain.account.Account
import app.data.domain.menu.Menu
import app.data.domain.role.Role
data class AccountRoleAccessDTO(
val account: Account,

View File

@ -1,6 +1,6 @@
package app.base.domain.auth.modle
package app.data.domain.account.modle
import app.domain.role.Role
import app.data.domain.role.Role
data class AccountRoleDTO(
val id: Long,

View File

@ -1,4 +1,4 @@
package app.domain.menu
package app.data.domain.menu
import org.aikrai.vertx.db.annotation.IdType
import org.aikrai.vertx.db.annotation.TableId

View File

@ -1,7 +1,6 @@
package app.domain.menu
package app.data.domain.menu
import app.base.domain.auth.menu.MenuRepository
import app.domain.account.Account
import app.data.domain.account.Account
import com.google.inject.Inject
import com.google.inject.Singleton
import io.vertx.ext.auth.User

View File

@ -1,7 +1,5 @@
package app.base.domain.auth.menu
package app.data.domain.menu
import app.domain.menu.Menu
import app.domain.menu.MenuRepositoryImpl
import com.google.inject.ImplementedBy
import org.aikrai.vertx.db.Repository

View File

@ -1,6 +1,5 @@
package app.domain.menu
package app.data.domain.menu
import app.base.domain.auth.menu.MenuRepository
import com.google.inject.Inject
import io.vertx.sqlclient.SqlClient
import org.aikrai.vertx.db.RepositoryImpl

View File

@ -1,6 +1,6 @@
package app.base.domain.auth.menu.modle
package app.data.domain.menu.modle
import app.domain.menu.Menu
import app.data.domain.menu.Menu
class TreeSelect {

View File

@ -1,4 +1,4 @@
package app.domain.role
package app.data.domain.role
import org.aikrai.vertx.db.annotation.TableName
import org.aikrai.vertx.utlis.BaseEntity

View File

@ -1,4 +1,4 @@
package app.domain.role
package app.data.domain.role
import com.google.inject.ImplementedBy
import org.aikrai.vertx.db.Repository

View File

@ -1,4 +1,4 @@
package app.domain.role
package app.data.domain.role
import com.google.inject.Inject
import io.vertx.sqlclient.SqlClient

View File

@ -0,0 +1,27 @@
package app.data.emun
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
import org.aikrai.vertx.db.annotation.EnumValue
enum class DeleteEnum(private val code: Int, private val description: String) {
UNDELETED(0, "未删除"),
DELETED(2, "已删除");
@JsonValue
@EnumValue
fun getCode(): Int {
return this.code
}
override fun toString(): String {
return this.description
}
companion object {
@JsonCreator
fun parse(code: Int): DeleteEnum? {
return entries.find { it.code == code }
}
}
}

View File

@ -0,0 +1,27 @@
package app.data.emun
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
import org.aikrai.vertx.db.annotation.EnumValue
enum class Status(private val code: Int, private val description: String) {
ACTIVE(0, "正常"),
INACTIVE(1, "禁用"),
DELETED(2, "删除");
@JsonValue
@EnumValue
public fun getCode(): Int {
return code
}
override fun toString(): String {
return description
}
companion object {
@JsonCreator
fun parse(code: Int): Status? {
return entries.find { it.code == code }
}
}
}

View File

@ -1,18 +0,0 @@
package app.domain
enum class CargoType(val message: String) {
RECEIVING("收货"),
SHIPPING("发货"),
INTERNAL_TRANSFER("内部调拨")
;
companion object {
fun parse(value: String?): CargoType? {
if (value.isNullOrBlank()) return null
return CargoType.values().find { it.name == value || it.message == value }
}
}
}

View File

@ -1,9 +1,9 @@
package app.service.account
import app.config.auth.TokenService
import app.domain.account.Account
import app.domain.account.AccountRepository
import app.domain.account.LoginDTO
import app.data.domain.account.Account
import app.data.domain.account.AccountRepository
import app.data.domain.account.LoginDTO
import cn.hutool.core.lang.Snowflake
import cn.hutool.crypto.SecureUtil
import com.google.inject.Inject

View File

@ -4,7 +4,7 @@ import app.config.RespBean
import app.config.auth.JwtAuthenticationHandler
import app.config.auth.ResponseHandler
import app.config.auth.TokenService
import app.domain.account.Account
import app.data.domain.account.Account
import app.port.aipfox.ApifoxClient
import cn.hutool.core.lang.Snowflake
import com.google.inject.Inject

View File

@ -3,6 +3,7 @@ package org.aikrai.vertx.context
import cn.hutool.core.util.StrUtil
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.vertx.core.http.HttpMethod
import io.vertx.core.json.JsonObject
import io.vertx.ext.auth.User
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
@ -11,6 +12,8 @@ import kotlinx.coroutines.launch
import org.aikrai.vertx.auth.*
import org.aikrai.vertx.config.resp.DefaultResponseHandler
import org.aikrai.vertx.config.resp.ResponseHandlerInterface
import org.aikrai.vertx.db.annotation.EnumValue
import org.aikrai.vertx.jackson.JsonUtil
import org.aikrai.vertx.utlis.ClassUtil
import org.aikrai.vertx.utlis.Meta
import org.reflections.Reflections
@ -73,8 +76,10 @@ class RouterBuilder(
isList = parameter.type.classifier == List::class,
isComplex = !parameter.type.classifier.toString().startsWith("class kotlin.") &&
!parameter.type.classifier.toString().startsWith("class io.vertx") &&
!(parameter.type.javaType as Class<*>).isEnum &&
!parameter.type.javaType.javaClass.isEnum &&
parameter.type.javaType is Class<*>
parameter.type.javaType is Class<*>,
isEnum = typeClass?.isEnum ?: (parameter.type.javaType as Class<*>).isEnum
)
}
routeInfoCache[reqPath to httpMethod] =
@ -163,15 +168,6 @@ class RouterBuilder(
}
}
private fun getReqPath(prefix: String, clazz: Class<*>): String {
val basePath = if (prefix.isNotBlank()) {
StrUtil.toCamelCase(StrUtil.toUnderlineCase(prefix))
} else {
StrUtil.toCamelCase(StrUtil.toUnderlineCase(clazz.simpleName.removeSuffix("Controller")))
}
return "/$basePath".replace("//", "/")
}
private fun getReqPath(prefix: String, clazz: Class<*>, method: Method): String {
var classPath = if (prefix.isNotBlank()) {
StrUtil.toCamelCase(StrUtil.toUnderlineCase(prefix))
@ -189,31 +185,57 @@ class RouterBuilder(
val queryParams = ctx.queryParams().entries().associate { it.key to it.value }
val combinedParams = formAttributes + queryParams
// 解析Body
val bodyStr = if (!ctx.body().isEmpty) ctx.body().asString() else ""
val bodyAsMap = if (bodyStr.isNotBlank()) {
try {
objectMapper.readValue(bodyStr, Map::class.java) as Map<String, Any>
} catch (e: Exception) {
emptyMap()
}
} else {
emptyMap()
}
val bodyObj = if (!ctx.body().isEmpty) ctx.body().asJsonObject() else null
val bodyMap = bodyObj?.map ?: emptyMap()
paramsInfo.forEach { param ->
if (param.isList) {
val listParamValue = ctx.queryParams().getAll(param.name)
if (listParamValue.isEmpty() && !param.isNullable) throw IllegalArgumentException("Missing required parameter: ${param.name}")
params.add(listParamValue)
var value = ctx.queryParams().getAll(param.name)
if (value.isEmpty() && bodyMap[param.name] != null) {
value = (bodyMap[param.name] as Collection<*>).map { it.toString() }.toMutableList()
}
if (value.isEmpty() && !param.isNullable) {
throw IllegalArgumentException("Missing required parameter: ${param.name}")
}
params.add(value.ifEmpty { null })
return@forEach
}
if (param.isEnum) {
val value = sequenceOf(
combinedParams[param.name],
bodyMap[param.name]
).filterNotNull().map { it.toString() }.firstOrNull()
val enumValueMethod = param.type.methods.find { method ->
method.isAnnotationPresent(EnumValue::class.java)
}
val enumValue = param.type.enumConstants.firstOrNull { enumConstant ->
if (enumValueMethod != null) {
enumValueMethod.invoke(enumConstant).toString() == value
} else {
(enumConstant as Enum<*>).name == value
}
}
if (enumValue != null) params.add(enumValue)
return@forEach
}
if (param.isComplex) {
try {
val value = objectMapper.readValue(bodyStr, param.type)
params.add(value)
val value = sequenceOf(
if (paramsInfo.size == 1) bodyObj else null,
bodyMap[param.name]?.let { JsonUtil.toJsonObject(it) },
combinedParams[param.name]?.let { JsonObject(it) },
bodyObj
).filterNotNull().firstOrNull { !it.isEmpty }
if (value?.isEmpty == true && !param.isNullable) {
throw IllegalArgumentException("Missing required parameter: ${param.name}")
}
params.add(if (value == null || value.isEmpty) null else JsonUtil.parseObject(value, param.type))
return@forEach
} catch (e: Exception) {
if (!param.isNullable) throw IllegalArgumentException("Failed to parse request body for parameter: ${param.name}")
throw IllegalArgumentException(e.message, e)
}
}
@ -222,7 +244,7 @@ class RouterBuilder(
RoutingContext::class.java -> ctx
User::class.java -> ctx.user()
else -> {
val bodyValue = bodyAsMap[param.name]
val bodyValue = bodyMap[param.name]
val paramValue = bodyValue?.toString() ?: combinedParams[param.name]
when {
paramValue == null -> {
@ -254,16 +276,12 @@ class RouterBuilder(
* @return 转换为目标类型的参数值如果转换失败则返回 `null`
*/
private fun getParamValue(paramValue: String, type: Class<*>): Any? {
return when {
type.isEnum -> {
type.enumConstants.firstOrNull { (it as Enum<*>).name.equals(paramValue, ignoreCase = true) }
}
type == String::class.java -> paramValue
type == Int::class.java || type == Integer::class.java -> paramValue.toIntOrNull()
type == Long::class.java || type == Long::class.java -> paramValue.toLongOrNull()
type == Double::class.java || type == Double::class.java -> paramValue.toDoubleOrNull()
type == Boolean::class.java || type == Boolean::class.java -> paramValue.toBoolean()
return when (type) {
String::class.java -> paramValue
Int::class.java, Integer::class.java -> paramValue.toIntOrNull()
Long::class.java, Long::class.java -> paramValue.toLongOrNull()
Double::class.java, Double::class.java -> paramValue.toDoubleOrNull()
Boolean::class.java, Boolean::class.java -> paramValue.toBoolean()
else -> paramValue
}
}
@ -298,6 +316,21 @@ class RouterBuilder(
private fun serializeToJson(obj: Any?): String {
return objectMapper.writeValueAsString(obj)
}
private fun getEnumValue(enumValue: Any?): Any? {
if (enumValue == null || !enumValue::class.java.isEnum) {
return null // 不是枚举或为空,直接返回 null
}
val enumClass = enumValue::class.java
val methods = enumClass.declaredMethods
for (method in methods) {
if (method.isAnnotationPresent(EnumValue::class.java)) {
method.isAccessible = true // 如果方法是私有的,设置为可访问
return method.invoke(enumValue) // 调用带有 @EnumValue 注解的方法
}
}
return null // 没有找到带有 @EnumValue 注解的方法
}
}
private data class RouteInfo(
@ -316,6 +349,7 @@ class RouterBuilder(
val type: Class<*>,
val isNullable: Boolean,
val isList: Boolean,
val isComplex: Boolean
val isComplex: Boolean,
val isEnum: Boolean
)
}

View File

@ -263,6 +263,7 @@ class QueryWrapperImpl<T : Any>(
"IN", "NOT IN" -> {
params[it.column] = "(${(it.value as Collection<*>).joinToString(",")})"
}
else -> {
params[it.column] = it.value.toString()
}
@ -292,7 +293,7 @@ class QueryWrapperImpl<T : Any>(
.execute(params)
.coAwait()
.toList()
return objs.map { JsonUtil.parseObject(it.encode(), clazz) }.also { conditions.clear() }
return objs.map { JsonUtil.parseObject(it, clazz, true) }.also { conditions.clear() }
} catch (e: Exception) {
conditions.clear()
throw Meta.repository(e.javaClass.simpleName, e.message)

View File

@ -35,6 +35,7 @@ open class RepositoryImpl<TId, TEntity : Any>(
private val idFieldCache = ConcurrentHashMap<String, Field>()
private val idFieldNameCache = ConcurrentHashMap<String, String>()
private val tableNameCache = ConcurrentHashMap<String, String>()
// sqlCache
private val baseSqlCache = ConcurrentHashMap<String, ConcurrentHashMap<String, String>>()
private val queryClientCache = ConcurrentHashMap<String, Any>()
@ -93,7 +94,7 @@ open class RepositoryImpl<TId, TEntity : Any>(
val idAnnotation = idField.getAnnotation(TableId::class.java)
val idValue = idField.get(t)
val excludeId = idAnnotation != null && (idValue == null || idValue == 0L || idValue == -1L) &&
(idAnnotation.type == IdType.AUTO)
(idAnnotation.type == IdType.AUTO)
val sqlKey = if (excludeId) "createExcludeId" else "createIncludeId"
val sqlTemplate = getOrCreateSql(tableName, sqlKey) {
@ -120,6 +121,7 @@ open class RepositoryImpl<TId, TEntity : Any>(
IdType.INPUT -> {
if (idValue == 0L || idValue == -1L) throw Meta.repository("CreateError", "must provide ID value")
}
IdType.ASSIGN_ID -> params[idField.name] = IdUtil.getSnowflakeNextId()
IdType.ASSIGN_UUID -> params[idField.name] = IdUtil.simpleUUID()
else -> {}
@ -137,6 +139,7 @@ open class RepositoryImpl<TId, TEntity : Any>(
else -> null
}
}
else -> null
}
if (value != null) params[field.name] = value
@ -331,11 +334,36 @@ open class RepositoryImpl<TId, TEntity : Any>(
// 获取非空字段及其值
private fun getNonNullFields(t: TEntity): Map<String, Any> {
return fields.filter { !it.isAnnotationPresent(Transient::class.java) && it.get(t) != null }
.associate { it.name to it.get(t) }
return fields
.filter {
!it.isAnnotationPresent(Transient::class.java) && it.get(t) != null
}
.associate {
val value = it.get(t)
if (it.type.isEnum) {
it.name to (getEnumValue(value) ?: (value as Enum<*>).name)
} else {
it.name to value
}
}
}
/**
private fun getEnumValue(enumValue: Any?): Any? {
if (enumValue == null || !enumValue::class.java.isEnum) {
return null // 不是枚举或为空,直接返回 null
}
val enumClass = enumValue::class.java
val methods = enumClass.declaredMethods
for (method in methods) {
if (method.isAnnotationPresent(EnumValue::class.java)) {
method.isAccessible = true // 如果方法是私有的,设置为可访问
return method.invoke(enumValue) // 调用带有 @EnumValue 注解的方法
}
}
return null // 没有找到带有 @EnumValue 注解的方法
}
/**
* 生成批量 INSERT SQL 语句的函数
* @param objects 要插入的对象列表
* @return 生成的 SQL 语句字符串
@ -366,10 +394,13 @@ open class RepositoryImpl<TId, TEntity : Any>(
is Number, is Boolean -> value.toString() // 数字和布尔类型,直接转换为字符串
is Timestamp -> // 时间戳类型
"'${OffsetDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault())}'"
is Array<*> -> // 数组类型处理
if (value.isEmpty()) "'{}'" else "'{${value.joinToString(",") { escapeSql(it?.toString() ?: "NULL") }}}'"
is Collection<*> -> // 集合类型处理
if (value.isEmpty()) "'{}'" else "'{${value.joinToString(",") { escapeSql(it?.toString() ?: "NULL") }}}'"
else -> "'${escapeSql(value.toString())}'" // 其他类型,调用 toString() 后转义并加单引号
}
// 构建 VALUES 部分,每个对象对应一组值

View File

@ -1,5 +1,9 @@
package org.aikrai.vertx.db.annotation
import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.RetentionPolicy
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.ANNOTATION_CLASS)
@ -32,6 +36,11 @@ annotation class TableField(
// val numericScale: String = ""
)
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION)
annotation class EnumValue
/**
* IdType
* @property key Int

View File

@ -15,8 +15,9 @@ class ColumnAnnotationIntrospector : JacksonAnnotationIntrospector() {
}
private fun getColumnName(annotated: Annotated?): PropertyName? {
if (annotated == null) return null
val column = annotated.getAnnotation(TableField::class.java)
return column?.let { PropertyName(it.value) }
return null
// if (annotated == null) return null
// val column = annotated.getAnnotation(TableField::class.java)
// return column?.let { PropertyName(it.value) }
}
}

View File

@ -14,20 +14,27 @@ import io.swagger.v3.oas.models.parameters.Parameter
import io.swagger.v3.oas.models.parameters.RequestBody
import io.swagger.v3.oas.models.responses.ApiResponse
import io.swagger.v3.oas.models.responses.ApiResponses
import io.swagger.v3.oas.models.security.SecurityScheme
import io.swagger.v3.oas.models.servers.Server
import mu.KotlinLogging
import org.aikrai.vertx.context.Controller
import org.aikrai.vertx.context.CustomizeRequest
import org.aikrai.vertx.context.D
import org.aikrai.vertx.db.annotation.EnumValue
import org.aikrai.vertx.utlis.ClassUtil
import org.reflections.Reflections
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.sql.Date
import java.sql.Time
import java.sql.Timestamp
import java.time.*
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.kotlinFunction
@ -47,6 +54,8 @@ class OpenApiSpecGenerator {
private val PRIMITIVE_TYPE_MAPPING = mapOf(
// java.lang classes
String::class.java to "string",
Char::class.java to "string",
java.lang.Character::class.java to "string",
Int::class.java to "integer",
Integer::class.java to "integer",
Long::class.java to "number",
@ -222,35 +231,17 @@ class OpenApiSpecGenerator {
* @return ApiResponses 对象
*/
private fun generateResponsesFromReturnType(method: Method): ApiResponses {
val returnType = method.kotlinFunction?.returnType?.javaType
val schema = when (returnType) {
// 处理泛型返回类型
is ParameterizedType -> {
val rawType = returnType.rawType as Class<*>
val typeArguments = returnType.actualTypeArguments
when {
// 处理集合类型
Collection::class.java.isAssignableFrom(rawType) -> {
Schema<Any>().apply {
type = "array"
items = generateSchema(
typeArguments[0].let {
when (it) {
is Class<*> -> it
is ParameterizedType -> it.rawType as Class<*>
else -> Any::class.java
}
}
)
}
}
// 可以添加其他泛型类型的处理
else -> generateSchema(rawType)
}
}
// 处理普通类型
is Class<*> -> generateSchema(returnType)
else -> Schema<Any>().type("object")
val returnType = method.kotlinFunction?.returnType
// 创建 RespBean 的架构
val respBeanSchema = Schema<Any>().apply {
type = "object"
properties = mapOf(
"code" to Schema<Int>().type("integer").example(200),
"message" to Schema<String>().type("string").example("Success"),
"data" to generateDataSchema(returnType),
"requestId" to Schema<Long>().type("integer").format("int64").example(1899712678486753280)
)
required = listOf("code", "message", "data")
}
return ApiResponses().addApiResponse(
@ -259,13 +250,69 @@ class OpenApiSpecGenerator {
description = "OK"
content = Content().addMediaType(
"application/json",
MediaType().schema(schema)
MediaType().schema(respBeanSchema)
)
headers = mapOf()
}
)
}
// 新增辅助方法,用于生成 data 字段的 Schema
private fun generateDataSchema(returnType: KType?): Schema<Any> {
if (returnType == null) {
return Schema<Any>().type("null")
}
return when (val classifier = returnType.classifier) {
is KClass<*> -> {
when {
// 处理集合类型
Collection::class.java.isAssignableFrom(classifier.java) -> {
Schema<Any>().apply {
type = "array"
items = generateSchema(
(returnType.arguments.firstOrNull()?.type?.classifier as? KClass<*> ?: Any::class).java,
false
)
}
}
else -> generateSchema(classifier.java)
}
}
else -> Schema<Any>().type("object")
}
// return when (returnType) {
// // 处理泛型返回类型
// is ParameterizedType -> {
// val rawType = returnType.rawType as Class<*>
// val typeArguments = returnType.actualTypeArguments
// when {
// // 处理集合类型
// Collection::class.java.isAssignableFrom(rawType) -> {
// Schema<Any>().apply {
// type = "array"
// items = generateSchema(
// typeArguments[0].let {
// when (it) {
// is Class<*> -> it
// is ParameterizedType -> it.rawType as Class<*>
// else -> Any::class.java
// }
// }, isNullable
// )
// }
// }
// else -> generateSchema(rawType, isNullable)
// }
// }
// // 处理普通类型
// is Class<*> -> generateSchema(returnType, isNullable)
// null -> Schema<Any>().type("null")
// else -> Schema<Any>().type("object")
// }
}
/**
* 获取方法的参数列表
*
@ -329,7 +376,7 @@ class OpenApiSpecGenerator {
* @param type 参数类型
* @return OpenAPI Schema 对象
*/
private fun generateSchema(type: Class<*>): Schema<Any> {
private fun generateSchema(type: Class<*>, isNullable: Boolean = false): Schema<Any> {
// 如果该类型已经处理过,则返回一个空的 Schema避免循环引用
if (processedTypes.contains(type)) {
return Schema<Any>().apply {
@ -344,30 +391,66 @@ class OpenApiSpecGenerator {
PRIMITIVE_TYPE_MAPPING.containsKey(type) -> Schema<Any>().apply {
this.type = PRIMITIVE_TYPE_MAPPING[type]
deprecated = false
nullable = isNullable
}
// 处理枚举类型
type.isEnum -> Schema<Any>().apply {
this.type = "string"
enum = type.enumConstants?.map { it.toString() }
val enumValueMethod = type.methods.find { method ->
method.isAnnotationPresent(EnumValue::class.java)
}
this.type = if (enumValueMethod != null) {
when (enumValueMethod.returnType) {
String::class.java -> "string"
Int::class.java, java.lang.Integer::class.java -> "integer"
Long::class.java, java.lang.Long::class.java -> "integer"
Double::class.java, java.lang.Double::class.java -> "number"
Float::class.java, java.lang.Float::class.java -> "number"
Boolean::class.java, java.lang.Boolean::class.java -> "boolean"
else -> "string" // 默认情况下使用 string
}
} else {
"string" // 如果没有 enumValueMethod默认使用 string
}
enum = type.enumConstants?.map {
if (enumValueMethod != null) {
enumValueMethod.invoke(it)
} else {
it.toString()
}
}
nullable = isNullable
}
type.name.startsWith("java.lang") || type.name.startsWith("java.time") || type.name.startsWith("java.sql") -> Schema<Any>().apply {
this.type = type.simpleName.lowercase()
deprecated = false
nullable = isNullable
}
type.name.startsWith("java") -> Schema<Any>().apply {
this.type = type.simpleName.lowercase()
deprecated = false
nullable = isNullable
}
// 处理自定义对象
else -> Schema<Any>().apply {
this.type = "object"
properties = type.declaredFields
.filter { !it.isSynthetic }
.associate { field ->
field.isAccessible = true
field.name to generateSchema(field.type)
properties = type.kotlin.declaredMemberProperties
.associate { property ->
val field = property.javaField
field?.isAccessible = true
val nullable = property.returnType.isMarkedNullable
val fieldType = field?.type ?: Any::class.java
property.name to generateSchema(fieldType, nullable)
}
}
// 处理自定义对象
// else -> Schema<Any>().apply {
// this.type = "object"
// properties = type.declaredFields
// .filter { !it.isSynthetic }
// .associate { field ->
// field.isAccessible = true
// field.name to generateSchema(field.type, )
// }
// }
}.also {
// 处理完后,从已处理集合中移除当前类型
processedTypes.remove(type)