From 63a18f1dbdee4dc40db55e338a43b6aaf3a23a92 Mon Sep 17 00:00:00 2001 From: AiKrai Date: Wed, 19 Mar 2025 15:09:09 +0800 Subject: [PATCH] 1 --- .../app/example/SqlAnnotationExample.kt | 146 ++++++++ .../kotlin/app/util/SqlAnnotationMapper.kt | 187 ++++++++++ .../src/main/kotlin/app/util/SqlGenerator.kt | 182 ++++++++++ .../main/kotlin/app/util/sql/SqlGenExample.kt | 63 ---- .../kotlin/org/aikrai/vertx/gen/SqlGen.kt | 338 ------------------ .../kotlin/org/aikrai/vertx/gen/SqlGenCli.kt | 153 -------- 6 files changed, 515 insertions(+), 554 deletions(-) create mode 100644 vertx-demo/src/main/kotlin/app/example/SqlAnnotationExample.kt create mode 100644 vertx-demo/src/main/kotlin/app/util/SqlAnnotationMapper.kt create mode 100644 vertx-demo/src/main/kotlin/app/util/SqlGenerator.kt delete mode 100644 vertx-demo/src/main/kotlin/app/util/sql/SqlGenExample.kt delete mode 100644 vertx-fw/src/main/kotlin/org/aikrai/vertx/gen/SqlGen.kt delete mode 100644 vertx-fw/src/main/kotlin/org/aikrai/vertx/gen/SqlGenCli.kt diff --git a/vertx-demo/src/main/kotlin/app/example/SqlAnnotationExample.kt b/vertx-demo/src/main/kotlin/app/example/SqlAnnotationExample.kt new file mode 100644 index 0000000..743477e --- /dev/null +++ b/vertx-demo/src/main/kotlin/app/example/SqlAnnotationExample.kt @@ -0,0 +1,146 @@ +package app.example + +import app.util.* +import kotlin.reflect.KClass + +/** + * 表名注解示例 + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class TableName(val value: String) + +/** + * 列名注解示例 + */ +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +annotation class Column(val value: String, val type: String = "VARCHAR(255)") + +/** + * 主键注解示例 + */ +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +annotation class Id(val value: Boolean = true) + +/** + * 非空注解示例 + */ +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +annotation class NotNull(val value: Boolean = true) + +/** + * 默认值注解示例 + */ +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +annotation class DefaultValue(val value: String) + +/** + * 用户实体类示例 + */ +@TableName("tb_user") +class User { + @Id + @Column("id", "SERIAL") + @NotNull + var id: Long = 0 + + @Column("username") + @NotNull + var username: String = "" + + @Column("email") + var email: String? = null + + @Column("created_at", "TIMESTAMP") + @DefaultValue("CURRENT_TIMESTAMP") + var createdAt: String = "" +} + +/** + * SQL注解映射示例 + */ +class SqlAnnotationExample { + companion object { + /** + * 创建注解映射器 + */ + fun createMapper(): SqlAnnotationMapper { + val mapper = SqlAnnotationMapper() + + // 设置表名映射 + mapper.tableName = AnnotationMapping( + annotationClass = TableName::class, + propertyName = "value" + ) + + // 设置列名映射 + mapper.addColumnMapping(ColumnMapping( + nameMapping = AnnotationMapping( + annotationClass = Column::class, + propertyName = "value" + ), + typeMapping = AnnotationMapping( + annotationClass = Column::class, + propertyName = "type" + ), + nullableMapping = AnnotationMapping( + annotationClass = NotNull::class, + propertyName = "value" + ), + defaultValueMapping = AnnotationMapping( + annotationClass = DefaultValue::class, + propertyName = "value" + ) + )) + + // 设置主键映射 + mapper.primaryKeyMapping = AnnotationMapping( + annotationClass = Id::class, + propertyName = "value" + ) + + return mapper + } + + /** + * 测试SQL生成 + */ + fun testSqlGeneration() { + // 创建注解映射器 + val mapper = createMapper() + + // 生成各种SQL + val createTableSql = SqlGenerator.generateCreateTableSql(User::class, mapper) + val insertSql = SqlGenerator.generateInsertSql(User::class, mapper) + val updateSql = SqlGenerator.generateUpdateSql(User::class, mapper) + val deleteSql = SqlGenerator.generateDeleteSql(User::class, mapper) + val selectSql = SqlGenerator.generateSelectSql(User::class, mapper) + val selectByPkSql = SqlGenerator.generateSelectByPrimaryKeySql(User::class, mapper) + + // 打印SQL + println("Create Table SQL:") + println(createTableSql) + println("\nInsert SQL:") + println(insertSql) + println("\nUpdate SQL:") + println(updateSql) + println("\nDelete SQL:") + println(deleteSql) + println("\nSelect SQL:") + println(selectSql) + println("\nSelect By PK SQL:") + println(selectByPkSql) + } + } +} + +/** + * 主函数,用于演示 + */ +fun main() { + SqlAnnotationExample.testSqlGeneration() +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/util/SqlAnnotationMapper.kt b/vertx-demo/src/main/kotlin/app/util/SqlAnnotationMapper.kt new file mode 100644 index 0000000..aeeb362 --- /dev/null +++ b/vertx-demo/src/main/kotlin/app/util/SqlAnnotationMapper.kt @@ -0,0 +1,187 @@ +package app.util + +import kotlin.reflect.KClass + +/** + * SQL注解映射中间类 + * 用于记录从哪些注解获取SQL生成所需的信息 + */ +class SqlAnnotationMapper { + /** + * 表名映射信息 + */ + var tableName: AnnotationMapping? = null + + /** + * 列名映射信息列表 + */ + var columnMappings: MutableList = mutableListOf() + + /** + * 主键映射信息 + */ + var primaryKeyMapping: AnnotationMapping? = null + + /** + * 其他自定义映射 + */ + var customMappings: MutableMap = mutableMapOf() + + /** + * 添加一个列映射 + */ + fun addColumnMapping(columnMapping: ColumnMapping) { + columnMappings.add(columnMapping) + } + + /** + * 添加一个自定义映射 + */ + fun addCustomMapping(key: String, mapping: AnnotationMapping) { + customMappings[key] = mapping + } +} + +/** + * 注解映射类 + * 记录从哪个注解的哪个属性获取信息 + */ +data class AnnotationMapping( + /** 注解类 */ + val annotationClass: KClass, + /** 注解属性名 */ + val propertyName: String = "value" +) + +/** + * 列映射信息 + */ +data class ColumnMapping( + /** 字段名称映射 */ + val nameMapping: AnnotationMapping, + /** 字段类型映射,可选 */ + val typeMapping: AnnotationMapping? = null, + /** 是否可为空映射,可选 */ + val nullableMapping: AnnotationMapping? = null, + /** 默认值映射,可选 */ + val defaultValueMapping: AnnotationMapping? = null +) + +/** + * SQL注解映射生成器 + * 用于生成和使用SQL注解映射中间类 + */ +class SqlAnnotationMapperGenerator { + + companion object { + /** + * 从实体类获取SQL信息 + * @param entityClass 实体类 + * @param mapper 注解映射中间类 + * @return SQL信息 + */ + fun extractSqlInfo(entityClass: KClass<*>, mapper: SqlAnnotationMapper): SqlInfo { + val sqlInfo = SqlInfo() + + // 获取表名 + mapper.tableName?.let { tableNameMapping -> + val annotation = entityClass.annotations.find { it.annotationClass == tableNameMapping.annotationClass.java } + annotation?.let { + val method = it.javaClass.getMethod(tableNameMapping.propertyName) + sqlInfo.tableName = method.invoke(it) as String + } + } + + // 获取实体类的所有字段 + entityClass.java.declaredFields.forEach { field -> + // 获取列信息 + val columnInfo = ColumnInfo() + + // 处理每个列映射 + mapper.columnMappings.forEach { columnMapping -> + val nameAnnotation = field.annotations.find { + it.annotationClass.java == columnMapping.nameMapping.annotationClass.java + } + + if (nameAnnotation != null) { + val nameMethod = nameAnnotation.javaClass.getMethod(columnMapping.nameMapping.propertyName) + columnInfo.name = nameMethod.invoke(nameAnnotation) as String? ?: field.name + + // 处理类型映射 + columnMapping.typeMapping?.let { typeMapping -> + val typeAnnotation = field.annotations.find { + it.annotationClass.java == typeMapping.annotationClass.java + } + if (typeAnnotation != null) { + val typeMethod = typeAnnotation.javaClass.getMethod(typeMapping.propertyName) + columnInfo.type = typeMethod.invoke(typeAnnotation) as String + } + } + + // 处理可空映射 + columnMapping.nullableMapping?.let { nullableMapping -> + val nullableAnnotation = field.annotations.find { + it.annotationClass.java == nullableMapping.annotationClass.java + } + if (nullableAnnotation != null) { + val nullableMethod = nullableAnnotation.javaClass.getMethod(nullableMapping.propertyName) + columnInfo.nullable = nullableMethod.invoke(nullableAnnotation) as Boolean + } + } + + // 处理默认值映射 + columnMapping.defaultValueMapping?.let { defaultValueMapping -> + val defaultValueAnnotation = field.annotations.find { + it.annotationClass.java == defaultValueMapping.annotationClass.java + } + if (defaultValueAnnotation != null) { + val defaultValueMethod = defaultValueAnnotation.javaClass.getMethod(defaultValueMapping.propertyName) + columnInfo.defaultValue = defaultValueMethod.invoke(defaultValueAnnotation) as String + } + } + + sqlInfo.columns.add(columnInfo) + } + } + + // 处理主键 + mapper.primaryKeyMapping?.let { pkMapping -> + val pkAnnotation = field.annotations.find { + it.annotationClass.java == pkMapping.annotationClass.java + } + if (pkAnnotation != null) { + val pkMethod = pkAnnotation.javaClass.getMethod(pkMapping.propertyName) + val isPk = pkMethod.invoke(pkAnnotation) + if (isPk is Boolean && isPk) { + columnInfo.isPrimaryKey = true + sqlInfo.primaryKeys.add(columnInfo.name) + } + } + } + } + + return sqlInfo + } + } +} + +/** + * SQL信息类 + * 存储从实体类中提取的SQL相关信息 + */ +data class SqlInfo( + var tableName: String = "", + val columns: MutableList = mutableListOf(), + val primaryKeys: MutableList = mutableListOf() +) + +/** + * 列信息类 + */ +data class ColumnInfo( + var name: String = "", + var type: String = "", + var nullable: Boolean = true, + var defaultValue: String = "", + var isPrimaryKey: Boolean = false +) \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/util/SqlGenerator.kt b/vertx-demo/src/main/kotlin/app/util/SqlGenerator.kt new file mode 100644 index 0000000..ddb03e2 --- /dev/null +++ b/vertx-demo/src/main/kotlin/app/util/SqlGenerator.kt @@ -0,0 +1,182 @@ +package app.util + +import kotlin.reflect.KClass + +/** + * PostgreSQL SQL生成工具类 + */ +class SqlGenerator { + companion object { + /** + * 生成创建表SQL + * @param entityClass 实体类 + * @param mapper 注解映射中间类 + * @return 创建表SQL语句 + */ + fun generateCreateTableSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String { + val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper) + val tableName = sqlInfo.tableName + val columns = sqlInfo.columns + + if (tableName.isEmpty() || columns.isEmpty()) { + throw IllegalArgumentException("无法生成SQL,表名或列信息为空") + } + + val sb = StringBuilder() + sb.append("CREATE TABLE IF NOT EXISTS $tableName (\n") + + // 添加列定义 + val columnDefinitions = columns.map { column -> + val nullable = if (column.nullable) "" else " NOT NULL" + val defaultValue = if (column.defaultValue.isNotEmpty()) " DEFAULT ${column.defaultValue}" else "" + " ${column.name} ${column.type}$nullable$defaultValue" + } + + sb.append(columnDefinitions.joinToString(",\n")) + + // 添加主键 + if (sqlInfo.primaryKeys.isNotEmpty()) { + sb.append(",\n PRIMARY KEY (${sqlInfo.primaryKeys.joinToString(", ")})") + } + + sb.append("\n);") + return sb.toString() + } + + /** + * 生成插入SQL + * @param entityClass 实体类 + * @param mapper 注解映射中间类 + * @return 插入SQL语句模板 + */ + fun generateInsertSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String { + val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper) + val tableName = sqlInfo.tableName + val columns = sqlInfo.columns + + if (tableName.isEmpty() || columns.isEmpty()) { + throw IllegalArgumentException("无法生成SQL,表名或列信息为空") + } + + val columnNames = columns.map { it.name } + val placeholders = columns.mapIndexed { index, _ -> "$$${index + 1}" } + + return "INSERT INTO $tableName (${columnNames.joinToString(", ")}) VALUES (${placeholders.joinToString(", ")});" + } + + /** + * 生成更新SQL + * @param entityClass 实体类 + * @param mapper 注解映射中间类 + * @return 更新SQL语句模板 + */ + fun generateUpdateSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String { + val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper) + val tableName = sqlInfo.tableName + val columns = sqlInfo.columns.filter { !it.isPrimaryKey } + val primaryKeys = sqlInfo.columns.filter { it.isPrimaryKey } + + if (tableName.isEmpty() || columns.isEmpty() || primaryKeys.isEmpty()) { + throw IllegalArgumentException("无法生成SQL,表名、列信息或主键为空") + } + + val sb = StringBuilder() + sb.append("UPDATE $tableName SET ") + + // 设置列 + val setStatements = columns.mapIndexed { index, column -> + "${column.name} = $$${index + 1}" + } + sb.append(setStatements.joinToString(", ")) + + // 添加条件 + sb.append(" WHERE ") + val whereStatements = primaryKeys.mapIndexed { index, pk -> + "${pk.name} = $$${columns.size + index + 1}" + } + sb.append(whereStatements.joinToString(" AND ")) + + sb.append(";") + return sb.toString() + } + + /** + * 生成删除SQL + * @param entityClass 实体类 + * @param mapper 注解映射中间类 + * @return 删除SQL语句模板 + */ + fun generateDeleteSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String { + val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper) + val tableName = sqlInfo.tableName + val primaryKeys = sqlInfo.columns.filter { it.isPrimaryKey } + + if (tableName.isEmpty() || primaryKeys.isEmpty()) { + throw IllegalArgumentException("无法生成SQL,表名或主键为空") + } + + val sb = StringBuilder() + sb.append("DELETE FROM $tableName WHERE ") + + // 添加条件 + val whereStatements = primaryKeys.mapIndexed { index, pk -> + "${pk.name} = $$${index + 1}" + } + sb.append(whereStatements.joinToString(" AND ")) + + sb.append(";") + return sb.toString() + } + + /** + * 生成查询SQL + * @param entityClass 实体类 + * @param mapper 注解映射中间类 + * @return 查询SQL语句 + */ + fun generateSelectSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String { + val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper) + val tableName = sqlInfo.tableName + val columns = sqlInfo.columns + + if (tableName.isEmpty() || columns.isEmpty()) { + throw IllegalArgumentException("无法生成SQL,表名或列信息为空") + } + + val columnNames = columns.map { it.name } + + return "SELECT ${columnNames.joinToString(", ")} FROM $tableName;" + } + + /** + * 生成根据主键查询SQL + * @param entityClass 实体类 + * @param mapper 注解映射中间类 + * @return 根据主键查询SQL语句 + */ + fun generateSelectByPrimaryKeySql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String { + val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper) + val tableName = sqlInfo.tableName + val columns = sqlInfo.columns + val primaryKeys = sqlInfo.columns.filter { it.isPrimaryKey } + + if (tableName.isEmpty() || columns.isEmpty() || primaryKeys.isEmpty()) { + throw IllegalArgumentException("无法生成SQL,表名、列信息或主键为空") + } + + val columnNames = columns.map { it.name } + + val sb = StringBuilder() + sb.append("SELECT ${columnNames.joinToString(", ")} FROM $tableName WHERE ") + + // 添加条件 + val whereStatements = primaryKeys.mapIndexed { index, pk -> + "${pk.name} = $$${index + 1}" + } + sb.append(whereStatements.joinToString(" AND ")) + + sb.append(";") + return sb.toString() + } + } +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/util/sql/SqlGenExample.kt b/vertx-demo/src/main/kotlin/app/util/sql/SqlGenExample.kt deleted file mode 100644 index f3b178c..0000000 --- a/vertx-demo/src/main/kotlin/app/util/sql/SqlGenExample.kt +++ /dev/null @@ -1,63 +0,0 @@ -package app.util.sql - -import org.aikrai.vertx.gen.SqlGen -import kotlin.reflect.KClass - -/** - * SqlGen工具类使用示例 - */ -object SqlGenExample { - - /** - * 示例方法 - */ - @JvmStatic - fun main(args: Array) { - // 指定要扫描的包路径,例如"app.data.domain" - val packageName = "app.data.domain" - -// // 方式一:一步生成所有SQL -// val sqlMap = SqlGen.generateSql(packageName) -// -// // 打印生成的SQL -// println("=== 生成的SQL语句 ===") -// sqlMap.forEach { (key: String, sql: String) -> -// println("\n--- $key ---") -// println(sql) -// } - - // 方式二:分步骤生成 - println("\n\n=== 分步骤生成SQL ===") - val sqlGen = SqlGen() - - // 1. 扫描实体类 - println("1. 开始扫描实体类...") - val entities = sqlGen.scanEntities(packageName) - println("发现 ${entities.size} 个实体类:") - entities.forEach { entity: KClass<*> -> - println("- ${entity.simpleName}") - } - - // 2. 解析实体类信息到缓存 - println("\n2. 解析实体类信息...") - sqlGen.parseEntities(entities) - - // 3. 生成建表SQL - println("\n3. 生成建表SQL...") - val createTableSql = sqlGen.generateCreateTableSql() - createTableSql.forEach { sql: String -> - println(sql) - } - - // 4. 生成CRUD SQL - println("\n4. 生成CRUD SQL...") - val crudSql = sqlGen.generateCrudSql() - crudSql.forEach { (entityClass: KClass<*>, crud: SqlGen.CrudSql) -> - println("\n--- ${entityClass.simpleName} ---") - println("INSERT: ${crud.insertSql}") - println("SELECT: ${crud.selectByIdSql}") - println("UPDATE: ${crud.updateSql}") - println("DELETE: ${crud.deleteSql}") - } - } -} \ No newline at end of file diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/gen/SqlGen.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/gen/SqlGen.kt deleted file mode 100644 index 8f27140..0000000 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/gen/SqlGen.kt +++ /dev/null @@ -1,338 +0,0 @@ -package org.aikrai.vertx.gen - -import org.aikrai.vertx.db.annotation.* -import java.sql.Timestamp -import java.time.LocalDate -import java.time.LocalDateTime -import kotlin.reflect.KClass -import kotlin.reflect.KProperty -import kotlin.reflect.KProperty1 -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.memberProperties - -/** - * SQL生成工具类 - * 根据Kotlin实体类生成PostgreSQL SQL语句 - */ -class SqlGen { - - // 注解扫描器 - private val scanner = AnnotationScanner.create( - valueProperty = TableName::value, - defaultValueMapper = { klass -> camelToSnake(klass.simpleName!!) } - ) - - // 实体类缓存 - private val entityCache = mutableMapOf, EntityCache>() - - /** - * 扫描指定包路径下的所有带有@TableName注解的类 - * @param packageName 包名,例如 "app.data.domain" - * @return 带有@TableName注解的类列表 - */ - fun scanEntities(packageName: String): List> { - return scanner.scanClasses(packageName) - } - - /** - * 解析实体类并缓存信息 - * @param entityClasses 实体类列表 - */ - fun parseEntities(entityClasses: List>) { - // 使用scanner解析类 - scanner.parseClasses(entityClasses) - - // 将scanner的结果转换为entityCache - scanner.getAnnotatedClasses().forEach { (klass, annotatedClass) -> - val fields = mutableListOf() - val indices = mutableListOf() - - // 解析字段 - annotatedClass.properties.forEach { propertyInfo -> - val fieldCache = parseField(propertyInfo.property as KProperty<*>) - fields.add(fieldCache) - } - - // 解析索引 - val tableIndices = klass.findAnnotation() - if (tableIndices != null) { - val indexCache = IndexCache( - name = tableIndices.name.ifEmpty { "idx_${annotatedClass.value}_${tableIndices.columnNames.joinToString("_")}" }, - unique = tableIndices.unique, - concurrent = tableIndices.concurrent, - columnNames = tableIndices.columnNames.toList(), - definition = tableIndices.definition - ) - indices.add(indexCache) - } - - entityCache[klass] = EntityCache( - entityClass = klass, - tableName = annotatedClass.value, - fields = fields, - indices = indices - ) - } - } - - /** - * 解析字段 - */ - private fun parseField(property: KProperty<*>): FieldCache { - val tableId = property.findAnnotation() - val tableField = property.findAnnotation() - - val isPrimary = tableId != null - val idType = tableId?.type ?: IdType.NONE - val columnName = tableField?.value?.ifEmpty { camelToSnake(property.name) } ?: camelToSnake(property.name) - val fieldFill = tableField?.fill ?: FieldFill.DEFAULT - - val returnType = property.returnType - - return FieldCache( - property = property, - columnName = columnName, - isPrimary = isPrimary, - idType = idType, - fieldFill = fieldFill, - returnType = returnType - ) - } - - /** - * 生成所有实体类的建表SQL - * @return 建表SQL语句列表 - */ - fun generateCreateTableSql(): List { - return entityCache.values.map { generateCreateTableSql(it) } - } - - /** - * 为单个实体生成建表SQL - */ - private fun generateCreateTableSql(entityCache: EntityCache): String { - val sb = StringBuilder() - - sb.appendLine("-- 为实体 ${entityCache.entityClass.simpleName} 创建表") - sb.appendLine("CREATE TABLE IF NOT EXISTS \"${entityCache.tableName}\" (") - - // 添加字段定义 - entityCache.fields.forEachIndexed { index, field -> - sb.append(" \"${field.columnName}\" ${mapKotlinTypeToPostgresType(field)}") - - // 主键约束 - if (field.isPrimary) { - sb.append(" PRIMARY KEY") - } - - // 非空约束 (根据Kotlin类型是否为可空) - if (!field.returnType.isMarkedNullable && !field.isPrimary) { - sb.append(" NOT NULL") - } - - // 自增约束 - if (field.isPrimary && field.idType == IdType.AUTO) { - sb.append(" GENERATED BY DEFAULT AS IDENTITY") - } - - // 默认值 - when (field.fieldFill) { - FieldFill.INSERT, FieldFill.INSERT_UPDATE -> { - if (field.returnType.classifier == Timestamp::class || - field.returnType.classifier == LocalDateTime::class || - field.returnType.classifier == LocalDate::class) { - sb.append(" DEFAULT CURRENT_TIMESTAMP") - } - } - else -> {} - } - - if (index < entityCache.fields.size - 1) { - sb.appendLine(",") - } else { - sb.appendLine() - } - } - - sb.appendLine(");") - - // 添加索引 - entityCache.indices.forEach { index -> - sb.appendLine() - sb.append("-- 创建索引: ${index.name}") - sb.appendLine() - sb.append("CREATE ") - if (index.unique) sb.append("UNIQUE ") - sb.append("INDEX ") - if (index.concurrent) sb.append("CONCURRENTLY ") - sb.append("IF NOT EXISTS ${index.name} ON \"${entityCache.tableName}\" ") - if (index.definition.isNotEmpty()) { - sb.append(index.definition) - } else { - sb.append("(${index.columnNames.joinToString(", ") { "\"$it\"" }})") - } - sb.appendLine(";") - } - - return sb.toString() - } - - /** - * 生成基本的CRUD SQL语句 - */ - fun generateCrudSql(): Map, CrudSql> { - return entityCache.mapValues { (_, entityCache) -> - val tableName = entityCache.tableName - val fields = entityCache.fields - - val primaryKeyField = fields.find { it.isPrimary } - val regularFields = fields.filter { !it.isPrimary } - - val insertFields = regularFields.filter { field -> - when (field.fieldFill) { - FieldFill.INSERT, FieldFill.INSERT_UPDATE -> true - else -> true - } - } - - val updateFields = regularFields.filter { field -> - when (field.fieldFill) { - FieldFill.UPDATE, FieldFill.INSERT_UPDATE -> true - else -> true - } - } - - val fieldList = fields.joinToString(", ") { "\"${it.columnName}\"" } - val primaryKeyColumn = primaryKeyField?.columnName ?: "id" - - val insertFieldList = insertFields.joinToString(", ") { "\"${it.columnName}\"" } - val insertValuesList = insertFields.joinToString(", ") { ":${it.property.name}" } - - val updateSetList = updateFields.joinToString(", ") { "\"${it.columnName}\" = :${it.property.name}" } - - CrudSql( - insertSql = "INSERT INTO \"$tableName\" ($insertFieldList) VALUES ($insertValuesList);", - selectByIdSql = "SELECT $fieldList FROM \"$tableName\" WHERE \"$primaryKeyColumn\" = :id;", - updateSql = "UPDATE \"$tableName\" SET $updateSetList WHERE \"$primaryKeyColumn\" = :id;", - deleteSql = "DELETE FROM \"$tableName\" WHERE \"$primaryKeyColumn\" = :id;" - ) - } - } - - /** - * 将驼峰命名转换为下划线命名 - */ - private fun camelToSnake(name: String): String { - return name.replace(Regex("([a-z0-9])([A-Z])"), "$1_$2").toLowerCase() - } - - /** - * 将Kotlin类型映射为PostgreSQL类型 - */ - private fun mapKotlinTypeToPostgresType(field: FieldCache): String { - val type = field.returnType.classifier - return when (type) { - String::class -> "VARCHAR(255)" - Int::class, Integer::class -> "INTEGER" - Long::class -> "BIGINT" - Double::class -> "DOUBLE PRECISION" - Float::class -> "REAL" - Boolean::class -> "BOOLEAN" - Timestamp::class, java.util.Date::class -> "TIMESTAMP" - LocalDateTime::class -> "TIMESTAMP" - LocalDate::class -> "DATE" - Char::class -> "CHAR(1)" - else -> { - // 处理枚举类型 - if (type is KClass<*> && type.java.isEnum) { - "VARCHAR(50)" - } else { - "JSONB" // 默认复杂类型存储为JSON - } - } - } - } - - /** - * 实体缓存类 - */ - data class EntityCache( - val entityClass: KClass<*>, - val tableName: String, - val fields: List, - val indices: List - ) - - /** - * 字段缓存类 - */ - data class FieldCache( - val property: KProperty<*>, - val columnName: String, - val isPrimary: Boolean, - val idType: IdType, - val fieldFill: FieldFill, - val returnType: kotlin.reflect.KType - ) - - /** - * 索引缓存类 - */ - data class IndexCache( - val name: String, - val unique: Boolean, - val concurrent: Boolean, - val columnNames: List, - val definition: String - ) - - /** - * CRUD SQL语句 - */ - data class CrudSql( - val insertSql: String, - val selectByIdSql: String, - val updateSql: String, - val deleteSql: String - ) - - companion object { - /** - * 快速生成指定包下所有实体类的SQL - */ - fun generateSql(packageName: String): Map { - val sqlGen = SqlGen() - val entities = sqlGen.scanEntities(packageName) - sqlGen.parseEntities(entities) - - val createTableSql = sqlGen.generateCreateTableSql() - val crudSql = sqlGen.generateCrudSql() - - val result = mutableMapOf() - - entities.forEachIndexed { index, entity -> - val tableName = sqlGen.scanner.getAnnotatedClass(entity)?.value - ?: camelToSnake(entity.simpleName!!) - - result["$tableName-create"] = createTableSql[index] - - val crud = crudSql[entity] - if (crud != null) { - result["$tableName-insert"] = crud.insertSql - result["$tableName-select"] = crud.selectByIdSql - result["$tableName-update"] = crud.updateSql - result["$tableName-delete"] = crud.deleteSql - } - } - - return result - } - - /** - * 将驼峰命名转换为下划线命名 - */ - private fun camelToSnake(name: String): String { - return name.replace(Regex("([a-z0-9])([A-Z])"), "$1_$2").toLowerCase() - } - } -} \ No newline at end of file diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/gen/SqlGenCli.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/gen/SqlGenCli.kt deleted file mode 100644 index 09c54d2..0000000 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/gen/SqlGenCli.kt +++ /dev/null @@ -1,153 +0,0 @@ -package org.aikrai.vertx.gen - -import java.io.File -import kotlin.system.exitProcess - -/** - * SqlGen命令行工具 - * - * 用法: - * SqlGenCli - * - * 参数: - * packageName - 要扫描的包名,例如 app.data.domain - * outputDir - SQL文件输出目录,可选,默认为 ./sql-gen - */ -object SqlGenCli { - - @JvmStatic - fun main(args: Array) { - if (args.isEmpty() || args[0] == "-h" || args[0] == "--help") { - printHelp() - exitProcess(0) - } - - val packageName = args[0] - val outputDir = if (args.size > 1) args[1] else "./sql-gen" - - println("开始从包 $packageName 生成SQL...") - println("输出目录: $outputDir") - - // 创建输出目录 - val outputDirFile = File(outputDir) - if (!outputDirFile.exists()) { - outputDirFile.mkdirs() - println("创建输出目录: $outputDir") - } - - try { - // 生成SQL - val sqlMap: Map = SqlGen.generateSql(packageName) - - // 按类型组织SQL - val createTableSql = mutableListOf() - val insertSql = mutableListOf() - val selectSql = mutableListOf() - val updateSql = mutableListOf() - val deleteSql = mutableListOf() - - // 分类SQL语句 - sqlMap.forEach { (key: String, sql: String) -> - when { - key.endsWith("-create") -> createTableSql.add(sql) - key.endsWith("-insert") -> insertSql.add(sql) - key.endsWith("-select") -> selectSql.add(sql) - key.endsWith("-update") -> updateSql.add(sql) - key.endsWith("-delete") -> deleteSql.add(sql) - else -> {} // 忽略其他类型 - } - } - - // 写入各类SQL文件 - File(outputDirFile, "tables.sql").writeText(createTableSql.joinToString("\n\n")) - File(outputDirFile, "inserts.sql").writeText(insertSql.joinToString("\n\n")) - File(outputDirFile, "selects.sql").writeText(selectSql.joinToString("\n\n")) - File(outputDirFile, "updates.sql").writeText(updateSql.joinToString("\n\n")) - File(outputDirFile, "deletes.sql").writeText(deleteSql.joinToString("\n\n")) - - // 写入完整SQL文件 - val allSql = """ - |-- ==================== - |-- 建表语句 - |-- ==================== - | - |${createTableSql.joinToString("\n\n")} - | - |-- ==================== - |-- 插入语句 - |-- ==================== - | - |${insertSql.joinToString("\n\n")} - | - |-- ==================== - |-- 查询语句 - |-- ==================== - | - |${selectSql.joinToString("\n\n")} - | - |-- ==================== - |-- 更新语句 - |-- ==================== - | - |${updateSql.joinToString("\n\n")} - | - |-- ==================== - |-- 删除语句 - |-- ==================== - | - |${deleteSql.joinToString("\n\n")} - """.trimMargin() - - File(outputDirFile, "all.sql").writeText(allSql) - - // 为每个实体类生成单独的SQL文件 - val tableNames = sqlMap.keys - .filter { k: String -> k.endsWith("-create") } - .map { k: String -> k.substring(0, k.length - 7) } - - tableNames.forEach { tableName: String -> - val entitySql = """ - |-- 表结构 - |${sqlMap["$tableName-create"] ?: ""} - | - |-- 插入操作 - |${sqlMap["$tableName-insert"] ?: ""} - | - |-- 查询操作 - |${sqlMap["$tableName-select"] ?: ""} - | - |-- 更新操作 - |${sqlMap["$tableName-update"] ?: ""} - | - |-- 删除操作 - |${sqlMap["$tableName-delete"] ?: ""} - """.trimMargin() - - File(outputDirFile, "$tableName.sql").writeText(entitySql) - } - - println("SQL生成完成!") - println("成功生成 ${tableNames.size} 个实体类的SQL脚本到目录: $outputDir") - - } catch (e: Exception) { - System.err.println("生成SQL时发生错误: ${e.message}") - e.printStackTrace() - exitProcess(1) - } - } - - private fun printHelp() { - println(""" - |SqlGen - Kotlin实体类SQL生成工具 - | - |用法: SqlGenCli [outputDir] - | - |参数: - | packageName 要扫描的包名,例如 app.data.domain - | outputDir SQL文件输出目录,可选,默认为 ./sql-gen - | - |示例: - | SqlGenCli app.data.domain ./sql/generated - """.trimMargin()) - } -} \ No newline at end of file