From 5eccf7ceed4593c25de2435cffcc016b890d59b4 Mon Sep 17 00:00:00 2001 From: AiKrai Date: Thu, 13 Mar 2025 14:38:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor(router):=20=E4=BC=98=E5=8C=96=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E6=9E=84=E5=BB=BA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/app/port/aipfox/ApifoxClient.kt | 2 +- .../app/util}/openapi/OpenApiSpecGenerator.kt | 8 +- .../org/aikrai/vertx/context/RouterBuilder.kt | 460 ++++++++++-------- 3 files changed, 251 insertions(+), 219 deletions(-) rename {vertx-fw/src/main/kotlin/org/aikrai/vertx => vertx-demo/src/main/kotlin/app/util}/openapi/OpenApiSpecGenerator.kt (99%) diff --git a/vertx-demo/src/main/kotlin/app/port/aipfox/ApifoxClient.kt b/vertx-demo/src/main/kotlin/app/port/aipfox/ApifoxClient.kt index 936774c..81b5552 100644 --- a/vertx-demo/src/main/kotlin/app/port/aipfox/ApifoxClient.kt +++ b/vertx-demo/src/main/kotlin/app/port/aipfox/ApifoxClient.kt @@ -1,5 +1,6 @@ package app.port.aipfox +import app.util.openapi.OpenApiSpecGenerator import com.google.inject.Inject import com.google.inject.name.Named import io.vertx.core.Vertx @@ -8,7 +9,6 @@ import io.vertx.core.json.JsonObject import io.vertx.ext.web.client.WebClient import io.vertx.ext.web.client.WebClientOptions import mu.KotlinLogging -import org.aikrai.vertx.openapi.OpenApiSpecGenerator class ApifoxClient @Inject constructor( private val vertx: Vertx, diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/openapi/OpenApiSpecGenerator.kt b/vertx-demo/src/main/kotlin/app/util/openapi/OpenApiSpecGenerator.kt similarity index 99% rename from vertx-fw/src/main/kotlin/org/aikrai/vertx/openapi/OpenApiSpecGenerator.kt rename to vertx-demo/src/main/kotlin/app/util/openapi/OpenApiSpecGenerator.kt index c681380..01792ed 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/openapi/OpenApiSpecGenerator.kt +++ b/vertx-demo/src/main/kotlin/app/util/openapi/OpenApiSpecGenerator.kt @@ -1,4 +1,4 @@ -package org.aikrai.vertx.openapi +package app.util.openapi import cn.hutool.core.util.StrUtil import io.swagger.v3.core.util.Json @@ -14,7 +14,6 @@ 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 @@ -25,7 +24,6 @@ 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 @@ -219,7 +217,9 @@ class OpenApiSpecGenerator { private fun buildPath(controllerPrefix: String, methodName: String): String { val classPath = if (controllerPrefix != "/") { StrUtil.toCamelCase(StrUtil.toUnderlineCase(controllerPrefix)) - } else "" + } else { + "" + } val methodPath = StrUtil.toCamelCase(StrUtil.toUnderlineCase(methodName)) return "/$classPath/$methodPath".replace("//", "/") } diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/context/RouterBuilder.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/context/RouterBuilder.kt index 0fb96b8..1596367 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/context/RouterBuilder.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/context/RouterBuilder.kt @@ -1,7 +1,6 @@ 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 @@ -10,6 +9,7 @@ import io.vertx.ext.web.RoutingContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.aikrai.vertx.auth.* +import org.aikrai.vertx.auth.AuthUser.Companion.validateAuth import org.aikrai.vertx.config.resp.DefaultResponseHandler import org.aikrai.vertx.config.resp.ResponseHandlerInterface import org.aikrai.vertx.db.annotation.EnumValue @@ -19,209 +19,247 @@ import org.aikrai.vertx.utlis.Meta import org.reflections.Reflections import java.lang.reflect.Method import java.lang.reflect.ParameterizedType -import kotlin.collections.ArrayList import kotlin.coroutines.Continuation import kotlin.reflect.KFunction import kotlin.reflect.full.callSuspend import kotlin.reflect.full.declaredFunctions import kotlin.reflect.jvm.javaType +/** + * RouterBuilder - 基于注解控制器构建Vert.x路由的实用工具 + * + * 该类扫描控制器类,分析其方法,并将其注册为具有适当参数绑定和授权规则的HTTP端点 + */ class RouterBuilder( private val coroutineScope: CoroutineScope, private val router: Router, private val scanPath: String? = null, private val responseHandler: ResponseHandlerInterface = DefaultResponseHandler() ) { - var anonymousPaths = ArrayList() + // 不需要认证的路径集合 + val anonymousPaths = mutableListOf() + /** + * 基于注解控制器类构建路由 + * + * @param getIt 解析控制器实例的函数 + * @return 当前RouterBuilder实例(用于链式调用) + */ fun build(getIt: (clazz: Class<*>) -> Any): RouterBuilder { - // 缓存路由信息 - val routeInfoCache = mutableMapOf, RouteInfo>() - // 获取所有 Controller 类中的公共方法 - val packagePath = scanPath ?: ClassUtil.getMainClass().packageName - val controllerClassSet = Reflections(packagePath).getTypesAnnotatedWith(Controller::class.java) - val controllerMethods = ClassUtil.getPublicMethods(controllerClassSet) - for ((classType, methods) in controllerMethods) { - val controllerAnnotation = classType.getDeclaredAnnotationsByType(Controller::class.java).firstOrNull() - val prefixPath = controllerAnnotation?.prefix ?: "" - val classAllowAnonymous = classType.getAnnotation(AllowAnonymous::class.java) != null - for (method in methods) { - val reqPath = getReqPath(prefixPath, classType, method) - val httpMethod = getHttpMethod(method) - val allowAnonymous = method.getAnnotation(AllowAnonymous::class.java) != null - if (classAllowAnonymous || allowAnonymous) anonymousPaths.add(reqPath) - val customizeResp = method.getAnnotation(CustomizeResponse::class.java) != null - val role = method.getAnnotation(CheckRole::class.java) - val permissions = method.getAnnotation(CheckPermission::class.java) - val kFunction = classType.kotlin.declaredFunctions.find { it.name == method.name } - if (kFunction != null) { - val parameterInfo = kFunction.parameters.mapNotNull { parameter -> - val javaType = parameter.type.javaType - // 跳过协程的 Continuation 参数 - if (javaType is Class<*> && Continuation::class.java.isAssignableFrom(javaType)) { - return@mapNotNull null - } - parameter.name ?: return@mapNotNull null - val annotation = parameter.annotations.find { it is D } as? D - val paramName = annotation?.name?.takeIf { it.isNotBlank() } ?: parameter.name ?: "" - val typeClass = when (javaType) { - is Class<*> -> javaType - is ParameterizedType -> javaType.rawType as? Class<*> - else -> null - } - ParameterInfo( - name = paramName, - type = typeClass ?: parameter.type.javaType as Class<*>, - isNullable = parameter.type.isMarkedNullable, - 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<*>, - isEnum = typeClass?.isEnum ?: (parameter.type.javaType as Class<*>).isEnum - ) - } - routeInfoCache[reqPath to httpMethod] = - RouteInfo(classType, method, kFunction, parameterInfo, customizeResp, role, permissions) - } - } - } + // 扫描并缓存控制器路由信息 + val routeInfoCache = scanControllerRoutes() // 注册路由处理器 - routeInfoCache.forEach { (path, routeInfo) -> - router.route(routeInfo.httpMethod, path.first).handler { ctx -> - if (ctx.user() != null) { - val user = ctx.user() as AuthUser - if (!user.validateAuth(routeInfo)) { - ctx.fail(403, Meta.unauthorized("unauthorized")) - return@handler - } - } - val instance = getIt(routeInfo.classType) - buildLambda(ctx, instance, routeInfo) - } - } + registerRouteHandlers(routeInfoCache, getIt) + return this } - private fun buildLambda(ctx: RoutingContext, instance: Any, routeInfo: RouteInfo) { + /** + * 扫描控制器类并提取路由信息 + */ + private fun scanControllerRoutes(): Map, RouteInfo> { + val routeInfoCache = mutableMapOf, RouteInfo>() + val packagePath = scanPath ?: ClassUtil.getMainClass().packageName + val controllerClassSet = Reflections(packagePath).getTypesAnnotatedWith(Controller::class.java) + val controllerMethods = ClassUtil.getPublicMethods(controllerClassSet) + + for ((classType, methods) in controllerMethods) { + processControllerClass(classType, methods.toList(), routeInfoCache) + } + + return routeInfoCache + } + + /** + * 处理控制器类及其方法 + */ + private fun processControllerClass( + classType: Class<*>, + methods: List, + routeInfoCache: MutableMap, RouteInfo> + ) { + val controllerAnnotation = classType.getDeclaredAnnotationsByType(Controller::class.java).firstOrNull() + val prefixPath = controllerAnnotation?.prefix ?: "" + val classAllowAnonymous = classType.getAnnotation(AllowAnonymous::class.java) != null + + for (method in methods) { + val reqPath = getReqPath(prefixPath, classType, method) + val httpMethod = getHttpMethod(method) + + // 处理匿名访问 + if (classAllowAnonymous || method.getAnnotation(AllowAnonymous::class.java) != null) { + anonymousPaths.add(reqPath) + } + + // 提取方法元数据 + val customizeResp = method.getAnnotation(CustomizeResponse::class.java) != null + val role = method.getAnnotation(CheckRole::class.java) + val permissions = method.getAnnotation(CheckPermission::class.java) + + // 查找对应的Kotlin函数 + classType.kotlin.declaredFunctions.find { it.name == method.name }?.let { kFunction -> + val parameterInfo = extractParameterInfo(kFunction) + routeInfoCache[reqPath to httpMethod] = RouteInfo( + classType, method, kFunction, parameterInfo, + customizeResp, role, permissions, httpMethod + ) + } + } + } + + /** + * 从Kotlin函数中提取参数信息 + */ + private fun extractParameterInfo(kFunction: KFunction<*>): List { + return kFunction.parameters.mapNotNull { parameter -> + val javaType = parameter.type.javaType + + // 跳过协程的Continuation参数 + if (javaType is Class<*> && Continuation::class.java.isAssignableFrom(javaType)) { + return@mapNotNull null + } + + // 参数必须具有名称 + val paramName = parameter.name ?: return@mapNotNull null + + // 从D注解获取自定义参数名 + val annotation = parameter.annotations.find { it is D } as? D + val finalParamName = annotation?.name?.takeIf { it.isNotBlank() } ?: paramName + + // 确定参数类型 + val typeClass = when (javaType) { + is Class<*> -> javaType + is ParameterizedType -> javaType.rawType as? Class<*> + else -> null + } ?: parameter.type.javaType as Class<*> + + // 处理枚举类型参数 + val isEnum = typeClass.isEnum || parameter.type.javaType.javaClass.isEnum + val enumValueMethod = if (isEnum) { + typeClass.methods.find { it.isAnnotationPresent(EnumValue::class.java) } + } else null + + val enumConstants = if (isEnum) { + typeClass.enumConstants?.associateBy({ + enumValueMethod?.invoke(it)?.toString() ?: (it as Enum<*>).name + }, { it }) + } else emptyMap() + + // 检查是否是复杂类型 + val isComplex = !parameter.type.classifier.toString().startsWith("class kotlin.") && + !parameter.type.classifier.toString().startsWith("class io.vertx") && + !typeClass.isEnum && + !parameter.type.javaType.javaClass.isEnum && + parameter.type.javaType is Class<*> + + ParameterInfo( + name = finalParamName, + type = typeClass, + isNullable = parameter.type.isMarkedNullable, + isList = parameter.type.classifier == List::class, + isComplex = isComplex, + isEnum = isEnum, + enumValueMethod = enumValueMethod, + enumConstants = enumConstants + ) + } + } + + /** + * 为缓存的路由注册路由处理程序 + */ + private fun registerRouteHandlers( + routeInfoCache: Map, RouteInfo>, + getIt: (clazz: Class<*>) -> Any + ) { + routeInfoCache.forEach { (pathMethod, routeInfo) -> + val (path, _) = pathMethod + router.route(routeInfo.httpMethod, path).handler { ctx -> + handleRequest(ctx, getIt, routeInfo) + } + } + } + + /** + * 处理传入的HTTP请求 + */ + private fun handleRequest(ctx: RoutingContext, getIt: (clazz: Class<*>) -> Any, routeInfo: RouteInfo) { + // 如果存在用户,检查授权 + ctx.user()?.let { user -> + if (user is AuthUser) { + try { + user.validateAuth(routeInfo.role, routeInfo.permissions) + } catch (e: Throwable) { + ctx.fail(403, Meta.unauthorized("未授权")) + return + } + } + } + + // 获取控制器实例并执行方法 + val instance = getIt(routeInfo.classType) + executeControllerMethod(ctx, instance, routeInfo) + } + + /** + * 在协程中执行控制器方法 + */ + private fun executeControllerMethod(ctx: RoutingContext, instance: Any, routeInfo: RouteInfo) { coroutineScope.launch { try { - val params = getParamsInstance(ctx, routeInfo.parameterInfo) - val resObj = if (routeInfo.kFunction.isSuspend) { + val params = resolveMethodParameters(ctx, routeInfo.parameterInfo) + val result = if (routeInfo.kFunction.isSuspend) { routeInfo.kFunction.callSuspend(instance, *params) } else { routeInfo.kFunction.call(instance, *params) } - responseHandler.normal(ctx, resObj, routeInfo.customizeResp) + responseHandler.normal(ctx, result, routeInfo.customizeResp) } catch (e: Throwable) { responseHandler.exception(ctx, e) } } } - companion object { - private val objectMapper = jacksonObjectMapper() + /** + * 从请求中解析方法参数 + */ + private fun resolveMethodParameters(ctx: RoutingContext, paramsInfo: List): Array { + val params = mutableListOf() - private fun AuthUser.validateAuth(routeInfo: RouteInfo): Boolean { - // 如果没有权限要求,直接返回true - if (routeInfo.role == null && routeInfo.permissions == null) return true - // 验证角色 - val hasValidRole = routeInfo.role?.let { role -> - val roleSet = attributes().getJsonArray("role").toSet() as Set - if (roleSet.isEmpty()) { - false - } else { - val reqRoleSet = (role.value + role.type).filter { it.isNotBlank() }.toSet() - validateSet(reqRoleSet, roleSet, role.mode) - } - } ?: true + // 从不同来源收集参数 + val formAttributes = ctx.request().formAttributes().associate { it.key to it.value } + val queryParams = ctx.queryParams().entries().associate { it.key to it.value } + val combinedParams = formAttributes + queryParams - // 验证权限 - val hasValidPermission = routeInfo.permissions?.let { permissions -> - val permissionSet = attributes().getJsonArray("permissions").toSet() as Set - val roleSet = attributes().getJsonArray("role").toSet() as Set - if (permissionSet.isEmpty() && roleSet.isEmpty()) { - false - } else { - if (permissions.orRole.isNotEmpty()) { - val roleBoolean = validateSet(permissions.orRole.toSet(), roleSet, Mode.AND) - if (roleBoolean) return true - } - val reqPermissionSet = (permissions.value + permissions.type).filter { it.isNotBlank() }.toSet() - validateSet(reqPermissionSet, permissionSet, permissions.mode) - } - } ?: true - return hasValidRole && hasValidPermission - } + // 解析请求体 + val bodyObj = ctx.body().takeUnless { it.isEmpty }?.asJsonObject() + val bodyMap = bodyObj?.map ?: emptyMap() - private fun validateSet( - required: Set, - actual: Set, - mode: Mode - ): Boolean { - if (required.isEmpty()) return true - return when (mode) { - Mode.AND -> required == actual - Mode.OR -> required.any { it in actual } - } - } - - private fun getReqPath(prefix: String, clazz: Class<*>, method: Method): String { - var classPath = if (prefix.isNotBlank()) { - StrUtil.toCamelCase(StrUtil.toUnderlineCase(prefix)) - } else { - StrUtil.toCamelCase(StrUtil.toUnderlineCase(clazz.simpleName.removeSuffix("Controller"))) - } - if (classPath == "/") classPath = "" - val methodName = StrUtil.toCamelCase(StrUtil.toUnderlineCase(method.name)) - return "/$classPath/$methodName".replace("//", "/") - } - - private fun getParamsInstance(ctx: RoutingContext, paramsInfo: List): Array { - val params = mutableListOf() - val formAttributes = ctx.request().formAttributes().associate { it.key to it.value } - val queryParams = ctx.queryParams().entries().associate { it.key to it.value } - val combinedParams = formAttributes + queryParams - // 解析Body - val bodyObj = if (!ctx.body().isEmpty) ctx.body().asJsonObject() else null - val bodyMap = bodyObj?.map ?: emptyMap() - - paramsInfo.forEach { param -> - if (param.isList) { + // 处理每个参数 + paramsInfo.forEach { param -> + when { + // 处理List类型参数 + param.isList -> { var value = ctx.queryParams().getAll(param.name) if (value.isEmpty() && bodyMap[param.name] != null) { - value = (bodyMap[param.name] as Collection<*>).map { it.toString() }.toMutableList() + value = (bodyMap[param.name] as? Collection<*>)?.map { it.toString() }?.toMutableList() ?: mutableListOf() } if (value.isEmpty() && !param.isNullable) { - throw IllegalArgumentException("Missing required parameter: ${param.name}") + throw IllegalArgumentException("缺少必要参数: ${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 + // 处理枚举类型参数 + param.isEnum -> { + val value = combinedParams[param.name] ?: bodyMap[param.name]?.toString() + val enumValue = param.enumConstants?.get(value) + params.add(enumValue) } - if (param.isComplex) { + // 处理复杂对象参数 + param.isComplex -> { try { val value = sequenceOf( if (paramsInfo.size == 1) bodyObj else null, @@ -229,68 +267,79 @@ class RouterBuilder( combinedParams[param.name]?.let { JsonObject(it) }, bodyObj ).filterNotNull().firstOrNull { !it.isEmpty } + if (value?.isEmpty == true && !param.isNullable) { - throw IllegalArgumentException("Missing required parameter: ${param.name}") + throw IllegalArgumentException("缺少必要参数: ${param.name}") } + params.add(if (value == null || value.isEmpty) null else JsonUtil.parseObject(value, param.type)) - return@forEach } catch (e: Exception) { throw IllegalArgumentException(e.message, e) } } - params.add( - when (param.type) { + // 处理特殊或基本类型参数 + else -> { + params.add(when (param.type) { RoutingContext::class.java -> ctx User::class.java -> ctx.user() else -> { val bodyValue = bodyMap[param.name] val paramValue = bodyValue?.toString() ?: combinedParams[param.name] + when { paramValue == null -> { - if (!param.isNullable) throw IllegalArgumentException("Missing required parameter: ${param.name}") else null + if (!param.isNullable) { + throw IllegalArgumentException("缺少必要参数: ${param.name}") + } else null } - else -> { - val value = getParamValue(paramValue.toString(), param.type) + val value = convertStringToType(paramValue.toString(), param.type) if (!param.isNullable && value == null) { - throw IllegalArgumentException("Missing required parameter: ${param.name}") - } else { - value - } + throw IllegalArgumentException("缺少必要参数: ${param.name}") + } else value } } } - } - ) + }) + } } + } - return params.toTypedArray() + return params.toTypedArray() + } + + companion object { + /** + * 根据类和方法信息构造请求路径 + */ + private fun getReqPath(prefix: String, clazz: Class<*>, method: Method): String { + val classPath = if (prefix.isNotBlank()) { + StrUtil.toCamelCase(StrUtil.toUnderlineCase(prefix)) + } else { + StrUtil.toCamelCase(StrUtil.toUnderlineCase(clazz.simpleName.removeSuffix("Controller"))) + }.let { if (it == "/") "" else it } + + val methodName = StrUtil.toCamelCase(StrUtil.toUnderlineCase(method.name)) + return "/$classPath/$methodName".replace("//", "/") } /** - * 将字符串参数值映射到目标类型。 - * - * @param paramValue 参数的字符串值。 - * @param type 目标 [Class] 类型。 - * @return 转换为目标类型的参数值,如果转换失败则返回 `null`。 + * 将字符串值转换为目标类型 */ - private fun getParamValue(paramValue: String, type: Class<*>): Any? { + private fun convertStringToType(paramValue: String, type: Class<*>): Any? { 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() + Long::class.java, java.lang.Long::class.java -> paramValue.toLongOrNull() + Double::class.java, java.lang.Double::class.java -> paramValue.toDoubleOrNull() + Boolean::class.java, java.lang.Boolean::class.java -> paramValue.toBoolean() else -> paramValue } } /** - * 根据 [CustomizeRequest] 注解确定给定 REST 方法的 HTTP 方法。 - * - * @param method 目标方法。 - * @return 对应的 [HttpMethod]。 + * 确定控制器方法的HTTP方法 */ fun getHttpMethod(method: Method): HttpMethod { val api = method.getAnnotation(CustomizeRequest::class.java) @@ -306,33 +355,11 @@ class RouterBuilder( HttpMethod.POST } } - - /** - * 将对象序列化为 JSON 表示。 - * - * @param obj 要序列化的对象。 - * @return JSON 字符串。 - */ - 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( val classType: Class<*>, val method: Method, @@ -341,15 +368,20 @@ class RouterBuilder( val customizeResp: Boolean, val role: CheckRole? = null, val permissions: CheckPermission? = null, - val httpMethod: HttpMethod = getHttpMethod(method) + val httpMethod: HttpMethod ) + /** + * 参数元数据数据类 + */ private data class ParameterInfo( val name: String, val type: Class<*>, val isNullable: Boolean, val isList: Boolean, val isComplex: Boolean, - val isEnum: Boolean + val isEnum: Boolean, + val enumValueMethod: Method? = null, + val enumConstants: Map? = null ) }