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) Config.init(vertx)
val getIt = InjectConfig.configure(vertx) val getIt = InjectConfig.configure(vertx)
val mainVerticle = getIt.getInstance(MainVerticle::class.java) val mainVerticle = getIt.getInstance(MainVerticle::class.java)
vertx.deployVerticle(mainVerticle).onComplete { vertx.deployVerticle(mainVerticle).onComplete {
if (it.failed()) { if (it.failed()) {
logger.error { "MainVerticle startup failed: ${it.cause()?.stackTraceToString()}" } logger.error { "MainVerticle startup failed: ${it.cause()?.stackTraceToString()}" }

View File

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

View File

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

View File

@ -1,8 +1,8 @@
package app.controller package app.controller
import app.domain.CargoType import app.data.domain.account.Account
import app.domain.account.Account import app.data.domain.account.AccountRepository
import app.domain.account.AccountRepository import app.data.emun.Status
import app.service.account.AccountService import app.service.account.AccountService
import com.google.inject.Inject import com.google.inject.Inject
import mu.KotlinLogging import mu.KotlinLogging
@ -28,13 +28,15 @@ class Demo1Controller @Inject constructor(
@D("name", "姓名") name: String?, @D("name", "姓名") name: String?,
@D("age", "年龄") age: Int?, @D("age", "年龄") age: Int?,
@D("list", "列表") list: List<String>?, @D("list", "列表") list: List<String>?,
@D("cargoType", "货物类型") cargoType: CargoType? @D("status", "状态-0正常,1禁用,2删除") status: Status?,
@D("account", "账号") account: Account?
) { ) {
logger.info { "你好" } logger.info { "你好" }
println(age) println(age)
println(list) println(list)
println("test-$name") println("test-$name")
println(cargoType) println(status)
println(account)
} }
@D("事务测试") @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.db.annotation.*
import org.aikrai.vertx.jackson.JsonUtil
import org.aikrai.vertx.utlis.BaseEntity import org.aikrai.vertx.utlis.BaseEntity
import java.sql.Timestamp import java.sql.Timestamp
@ -23,7 +25,7 @@ class Account : BaseEntity() {
var password: String? = null var password: String? = null
var status: Char? = null var status: Status? = Status.ACTIVE
var delFlag: Char? = null var delFlag: Char? = null
@ -31,4 +33,8 @@ class Account : BaseEntity() {
@TableField(fill = FieldFill.UPDATE) @TableField(fill = FieldFill.UPDATE)
var loginDate: Timestamp? = null 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.data.domain.account.modle.AccountRoleAccessDTO
import app.domain.account.modle.AccountRoleAccessDTO import app.data.domain.account.modle.AccountRoleDTO
import com.google.inject.ImplementedBy import com.google.inject.ImplementedBy
import org.aikrai.vertx.db.Repository 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.data.domain.account.modle.AccountRoleAccessDTO
import app.domain.account.modle.AccountRoleAccessDTO import app.data.domain.account.modle.AccountRoleDTO
import com.google.inject.Inject import com.google.inject.Inject
import io.vertx.sqlclient.SqlClient import io.vertx.sqlclient.SqlClient
import org.aikrai.vertx.db.RepositoryImpl import org.aikrai.vertx.db.RepositoryImpl

View File

@ -1,4 +1,4 @@
package app.domain.account package app.data.domain.account
data class LoginDTO( data class LoginDTO(
var username: String, 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.data.domain.account.Account
import app.domain.menu.Menu import app.data.domain.menu.Menu
import app.domain.role.Role import app.data.domain.role.Role
data class AccountRoleAccessDTO( data class AccountRoleAccessDTO(
val account: Account, 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( data class AccountRoleDTO(
val id: Long, 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.IdType
import org.aikrai.vertx.db.annotation.TableId 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.data.domain.account.Account
import app.domain.account.Account
import com.google.inject.Inject import com.google.inject.Inject
import com.google.inject.Singleton import com.google.inject.Singleton
import io.vertx.ext.auth.User 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 com.google.inject.ImplementedBy
import org.aikrai.vertx.db.Repository 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 com.google.inject.Inject
import io.vertx.sqlclient.SqlClient import io.vertx.sqlclient.SqlClient
import org.aikrai.vertx.db.RepositoryImpl 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 { 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.db.annotation.TableName
import org.aikrai.vertx.utlis.BaseEntity 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 com.google.inject.ImplementedBy
import org.aikrai.vertx.db.Repository 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 com.google.inject.Inject
import io.vertx.sqlclient.SqlClient 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 package app.service.account
import app.config.auth.TokenService import app.config.auth.TokenService
import app.domain.account.Account import app.data.domain.account.Account
import app.domain.account.AccountRepository import app.data.domain.account.AccountRepository
import app.domain.account.LoginDTO import app.data.domain.account.LoginDTO
import cn.hutool.core.lang.Snowflake import cn.hutool.core.lang.Snowflake
import cn.hutool.crypto.SecureUtil import cn.hutool.crypto.SecureUtil
import com.google.inject.Inject import com.google.inject.Inject

View File

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

View File

@ -3,6 +3,7 @@ package org.aikrai.vertx.context
import cn.hutool.core.util.StrUtil import cn.hutool.core.util.StrUtil
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.vertx.core.http.HttpMethod import io.vertx.core.http.HttpMethod
import io.vertx.core.json.JsonObject
import io.vertx.ext.auth.User import io.vertx.ext.auth.User
import io.vertx.ext.web.Router import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext import io.vertx.ext.web.RoutingContext
@ -11,6 +12,8 @@ import kotlinx.coroutines.launch
import org.aikrai.vertx.auth.* import org.aikrai.vertx.auth.*
import org.aikrai.vertx.config.resp.DefaultResponseHandler import org.aikrai.vertx.config.resp.DefaultResponseHandler
import org.aikrai.vertx.config.resp.ResponseHandlerInterface 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.ClassUtil
import org.aikrai.vertx.utlis.Meta import org.aikrai.vertx.utlis.Meta
import org.reflections.Reflections import org.reflections.Reflections
@ -73,8 +76,10 @@ class RouterBuilder(
isList = parameter.type.classifier == List::class, isList = parameter.type.classifier == List::class,
isComplex = !parameter.type.classifier.toString().startsWith("class kotlin.") && isComplex = !parameter.type.classifier.toString().startsWith("class kotlin.") &&
!parameter.type.classifier.toString().startsWith("class io.vertx") && !parameter.type.classifier.toString().startsWith("class io.vertx") &&
!(parameter.type.javaType as Class<*>).isEnum &&
!parameter.type.javaType.javaClass.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] = 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 { private fun getReqPath(prefix: String, clazz: Class<*>, method: Method): String {
var classPath = if (prefix.isNotBlank()) { var classPath = if (prefix.isNotBlank()) {
StrUtil.toCamelCase(StrUtil.toUnderlineCase(prefix)) StrUtil.toCamelCase(StrUtil.toUnderlineCase(prefix))
@ -189,31 +185,57 @@ class RouterBuilder(
val queryParams = ctx.queryParams().entries().associate { it.key to it.value } val queryParams = ctx.queryParams().entries().associate { it.key to it.value }
val combinedParams = formAttributes + queryParams val combinedParams = formAttributes + queryParams
// 解析Body // 解析Body
val bodyStr = if (!ctx.body().isEmpty) ctx.body().asString() else "" val bodyObj = if (!ctx.body().isEmpty) ctx.body().asJsonObject() else null
val bodyAsMap = if (bodyStr.isNotBlank()) { val bodyMap = bodyObj?.map ?: emptyMap()
try {
objectMapper.readValue(bodyStr, Map::class.java) as Map<String, Any>
} catch (e: Exception) {
emptyMap()
}
} else {
emptyMap()
}
paramsInfo.forEach { param -> paramsInfo.forEach { param ->
if (param.isList) { if (param.isList) {
val listParamValue = ctx.queryParams().getAll(param.name) var value = ctx.queryParams().getAll(param.name)
if (listParamValue.isEmpty() && !param.isNullable) throw IllegalArgumentException("Missing required parameter: ${param.name}") if (value.isEmpty() && bodyMap[param.name] != null) {
params.add(listParamValue) 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 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) { if (param.isComplex) {
try { try {
val value = objectMapper.readValue(bodyStr, param.type) val value = sequenceOf(
params.add(value) 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 return@forEach
} catch (e: Exception) { } 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 RoutingContext::class.java -> ctx
User::class.java -> ctx.user() User::class.java -> ctx.user()
else -> { else -> {
val bodyValue = bodyAsMap[param.name] val bodyValue = bodyMap[param.name]
val paramValue = bodyValue?.toString() ?: combinedParams[param.name] val paramValue = bodyValue?.toString() ?: combinedParams[param.name]
when { when {
paramValue == null -> { paramValue == null -> {
@ -254,16 +276,12 @@ class RouterBuilder(
* @return 转换为目标类型的参数值如果转换失败则返回 `null` * @return 转换为目标类型的参数值如果转换失败则返回 `null`
*/ */
private fun getParamValue(paramValue: String, type: Class<*>): Any? { private fun getParamValue(paramValue: String, type: Class<*>): Any? {
return when { return when (type) {
type.isEnum -> { String::class.java -> paramValue
type.enumConstants.firstOrNull { (it as Enum<*>).name.equals(paramValue, ignoreCase = true) } Int::class.java, Integer::class.java -> paramValue.toIntOrNull()
} Long::class.java, Long::class.java -> paramValue.toLongOrNull()
Double::class.java, Double::class.java -> paramValue.toDoubleOrNull()
type == String::class.java -> paramValue Boolean::class.java, Boolean::class.java -> paramValue.toBoolean()
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()
else -> paramValue else -> paramValue
} }
} }
@ -298,6 +316,21 @@ class RouterBuilder(
private fun serializeToJson(obj: Any?): String { private fun serializeToJson(obj: Any?): String {
return objectMapper.writeValueAsString(obj) 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( private data class RouteInfo(
@ -316,6 +349,7 @@ class RouterBuilder(
val type: Class<*>, val type: Class<*>,
val isNullable: Boolean, val isNullable: Boolean,
val isList: 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" -> { "IN", "NOT IN" -> {
params[it.column] = "(${(it.value as Collection<*>).joinToString(",")})" params[it.column] = "(${(it.value as Collection<*>).joinToString(",")})"
} }
else -> { else -> {
params[it.column] = it.value.toString() params[it.column] = it.value.toString()
} }
@ -292,7 +293,7 @@ class QueryWrapperImpl<T : Any>(
.execute(params) .execute(params)
.coAwait() .coAwait()
.toList() .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) { } catch (e: Exception) {
conditions.clear() conditions.clear()
throw Meta.repository(e.javaClass.simpleName, e.message) 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 idFieldCache = ConcurrentHashMap<String, Field>()
private val idFieldNameCache = ConcurrentHashMap<String, String>() private val idFieldNameCache = ConcurrentHashMap<String, String>()
private val tableNameCache = ConcurrentHashMap<String, String>() private val tableNameCache = ConcurrentHashMap<String, String>()
// sqlCache // sqlCache
private val baseSqlCache = ConcurrentHashMap<String, ConcurrentHashMap<String, String>>() private val baseSqlCache = ConcurrentHashMap<String, ConcurrentHashMap<String, String>>()
private val queryClientCache = ConcurrentHashMap<String, Any>() private val queryClientCache = ConcurrentHashMap<String, Any>()
@ -120,6 +121,7 @@ open class RepositoryImpl<TId, TEntity : Any>(
IdType.INPUT -> { IdType.INPUT -> {
if (idValue == 0L || idValue == -1L) throw Meta.repository("CreateError", "must provide ID value") if (idValue == 0L || idValue == -1L) throw Meta.repository("CreateError", "must provide ID value")
} }
IdType.ASSIGN_ID -> params[idField.name] = IdUtil.getSnowflakeNextId() IdType.ASSIGN_ID -> params[idField.name] = IdUtil.getSnowflakeNextId()
IdType.ASSIGN_UUID -> params[idField.name] = IdUtil.simpleUUID() IdType.ASSIGN_UUID -> params[idField.name] = IdUtil.simpleUUID()
else -> {} else -> {}
@ -137,6 +139,7 @@ open class RepositoryImpl<TId, TEntity : Any>(
else -> null else -> null
} }
} }
else -> null else -> null
} }
if (value != null) params[field.name] = value if (value != null) params[field.name] = value
@ -331,8 +334,33 @@ open class RepositoryImpl<TId, TEntity : Any>(
// 获取非空字段及其值 // 获取非空字段及其值
private fun getNonNullFields(t: TEntity): Map<String, Any> { private fun getNonNullFields(t: TEntity): Map<String, Any> {
return fields.filter { !it.isAnnotationPresent(Transient::class.java) && it.get(t) != null } return fields
.associate { it.name to it.get(t) } .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 注解的方法
} }
/** /**
@ -366,10 +394,13 @@ open class RepositoryImpl<TId, TEntity : Any>(
is Number, is Boolean -> value.toString() // 数字和布尔类型,直接转换为字符串 is Number, is Boolean -> value.toString() // 数字和布尔类型,直接转换为字符串
is Timestamp -> // 时间戳类型 is Timestamp -> // 时间戳类型
"'${OffsetDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault())}'" "'${OffsetDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault())}'"
is Array<*> -> // 数组类型处理 is Array<*> -> // 数组类型处理
if (value.isEmpty()) "'{}'" else "'{${value.joinToString(",") { escapeSql(it?.toString() ?: "NULL") }}}'" if (value.isEmpty()) "'{}'" else "'{${value.joinToString(",") { escapeSql(it?.toString() ?: "NULL") }}}'"
is Collection<*> -> // 集合类型处理 is Collection<*> -> // 集合类型处理
if (value.isEmpty()) "'{}'" else "'{${value.joinToString(",") { escapeSql(it?.toString() ?: "NULL") }}}'" if (value.isEmpty()) "'{}'" else "'{${value.joinToString(",") { escapeSql(it?.toString() ?: "NULL") }}}'"
else -> "'${escapeSql(value.toString())}'" // 其他类型,调用 toString() 后转义并加单引号 else -> "'${escapeSql(value.toString())}'" // 其他类型,调用 toString() 后转义并加单引号
} }
// 构建 VALUES 部分,每个对象对应一组值 // 构建 VALUES 部分,每个对象对应一组值

View File

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

View File

@ -15,8 +15,9 @@ class ColumnAnnotationIntrospector : JacksonAnnotationIntrospector() {
} }
private fun getColumnName(annotated: Annotated?): PropertyName? { private fun getColumnName(annotated: Annotated?): PropertyName? {
if (annotated == null) return null return null
val column = annotated.getAnnotation(TableField::class.java) // if (annotated == null) return null
return column?.let { PropertyName(it.value) } // 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.parameters.RequestBody
import io.swagger.v3.oas.models.responses.ApiResponse import io.swagger.v3.oas.models.responses.ApiResponse
import io.swagger.v3.oas.models.responses.ApiResponses 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 io.swagger.v3.oas.models.servers.Server
import mu.KotlinLogging import mu.KotlinLogging
import org.aikrai.vertx.context.Controller import org.aikrai.vertx.context.Controller
import org.aikrai.vertx.context.CustomizeRequest import org.aikrai.vertx.context.CustomizeRequest
import org.aikrai.vertx.context.D import org.aikrai.vertx.context.D
import org.aikrai.vertx.db.annotation.EnumValue
import org.aikrai.vertx.utlis.ClassUtil import org.aikrai.vertx.utlis.ClassUtil
import org.reflections.Reflections import org.reflections.Reflections
import java.lang.reflect.Method import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.sql.Date import java.sql.Date
import java.sql.Time import java.sql.Time
import java.sql.Timestamp import java.sql.Timestamp
import java.time.* import java.time.*
import kotlin.reflect.KClass
import kotlin.reflect.KParameter 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.javaType
import kotlin.reflect.jvm.kotlinFunction import kotlin.reflect.jvm.kotlinFunction
@ -47,6 +54,8 @@ class OpenApiSpecGenerator {
private val PRIMITIVE_TYPE_MAPPING = mapOf( private val PRIMITIVE_TYPE_MAPPING = mapOf(
// java.lang classes // java.lang classes
String::class.java to "string", String::class.java to "string",
Char::class.java to "string",
java.lang.Character::class.java to "string",
Int::class.java to "integer", Int::class.java to "integer",
Integer::class.java to "integer", Integer::class.java to "integer",
Long::class.java to "number", Long::class.java to "number",
@ -222,35 +231,17 @@ class OpenApiSpecGenerator {
* @return ApiResponses 对象 * @return ApiResponses 对象
*/ */
private fun generateResponsesFromReturnType(method: Method): ApiResponses { private fun generateResponsesFromReturnType(method: Method): ApiResponses {
val returnType = method.kotlinFunction?.returnType?.javaType val returnType = method.kotlinFunction?.returnType
val schema = when (returnType) { // 创建 RespBean 的架构
// 处理泛型返回类型 val respBeanSchema = Schema<Any>().apply {
is ParameterizedType -> { type = "object"
val rawType = returnType.rawType as Class<*> properties = mapOf(
val typeArguments = returnType.actualTypeArguments "code" to Schema<Int>().type("integer").example(200),
when { "message" to Schema<String>().type("string").example("Success"),
// 处理集合类型 "data" to generateDataSchema(returnType),
Collection::class.java.isAssignableFrom(rawType) -> { "requestId" to Schema<Long>().type("integer").format("int64").example(1899712678486753280)
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
}
}
) )
} required = listOf("code", "message", "data")
}
// 可以添加其他泛型类型的处理
else -> generateSchema(rawType)
}
}
// 处理普通类型
is Class<*> -> generateSchema(returnType)
else -> Schema<Any>().type("object")
} }
return ApiResponses().addApiResponse( return ApiResponses().addApiResponse(
@ -259,13 +250,69 @@ class OpenApiSpecGenerator {
description = "OK" description = "OK"
content = Content().addMediaType( content = Content().addMediaType(
"application/json", "application/json",
MediaType().schema(schema) MediaType().schema(respBeanSchema)
) )
headers = mapOf() 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 参数类型 * @param type 参数类型
* @return OpenAPI Schema 对象 * @return OpenAPI Schema 对象
*/ */
private fun generateSchema(type: Class<*>): Schema<Any> { private fun generateSchema(type: Class<*>, isNullable: Boolean = false): Schema<Any> {
// 如果该类型已经处理过,则返回一个空的 Schema避免循环引用 // 如果该类型已经处理过,则返回一个空的 Schema避免循环引用
if (processedTypes.contains(type)) { if (processedTypes.contains(type)) {
return Schema<Any>().apply { return Schema<Any>().apply {
@ -344,30 +391,66 @@ class OpenApiSpecGenerator {
PRIMITIVE_TYPE_MAPPING.containsKey(type) -> Schema<Any>().apply { PRIMITIVE_TYPE_MAPPING.containsKey(type) -> Schema<Any>().apply {
this.type = PRIMITIVE_TYPE_MAPPING[type] this.type = PRIMITIVE_TYPE_MAPPING[type]
deprecated = false deprecated = false
nullable = isNullable
} }
// 处理枚举类型 // 处理枚举类型
type.isEnum -> Schema<Any>().apply { type.isEnum -> Schema<Any>().apply {
this.type = "string" val enumValueMethod = type.methods.find { method ->
enum = type.enumConstants?.map { it.toString() } 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 { type.name.startsWith("java.lang") || type.name.startsWith("java.time") || type.name.startsWith("java.sql") -> Schema<Any>().apply {
this.type = type.simpleName.lowercase() this.type = type.simpleName.lowercase()
deprecated = false deprecated = false
nullable = isNullable
} }
type.name.startsWith("java") -> Schema<Any>().apply { type.name.startsWith("java") -> Schema<Any>().apply {
this.type = type.simpleName.lowercase() this.type = type.simpleName.lowercase()
deprecated = false deprecated = false
nullable = isNullable
} }
// 处理自定义对象
else -> Schema<Any>().apply { else -> Schema<Any>().apply {
this.type = "object" this.type = "object"
properties = type.declaredFields properties = type.kotlin.declaredMemberProperties
.filter { !it.isSynthetic } .associate { property ->
.associate { field -> val field = property.javaField
field.isAccessible = true field?.isAccessible = true
field.name to generateSchema(field.type) 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 { }.also {
// 处理完后,从已处理集合中移除当前类型 // 处理完后,从已处理集合中移除当前类型
processedTypes.remove(type) processedTypes.remove(type)