From 95f76404c262ab5cbebbc1e0a9d15e69f3af628c Mon Sep 17 00:00:00 2001 From: AiKrai Date: Mon, 12 May 2025 15:41:14 +0800 Subject: [PATCH] =?UTF-8?q?refactor(vertx-demo):=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../main/kotlin/app/config/InjectConfig.kt | 3 + .../app/config/provider/RedisProvider.kt | 24 + .../kotlin/app/controller/AuthController.kt | 2 +- .../kotlin/app/controller/Demo1Controller.kt | 2 +- .../kotlin/app/data/domain/account/Account.kt | 33 +- .../app/data/domain/role/RoleRepository.kt | 7 - .../account}/AccountRoleAccessDTO.kt | 2 +- .../modle => dto/account}/AccountRoleDTO.kt | 2 +- .../data/{domain => dto}/account/LoginDTO.kt | 4 +- .../modle => dto/account}/LoginUser.kt | 4 +- .../menu/modle => dto/menu}/RouterVo.kt | 0 .../menu/modle => dto/menu}/TreeSelect.kt | 2 +- .../data/{domain/menu => emun}/MenuType.kt | 6 +- .../src/main/kotlin/app/data/emun/SexType.kt | 28 + .../main/kotlin/app/port/reids/RedisClient.kt | 46 - .../AccountRepository.kt | 10 +- .../menu => repository}/MenuRepository.kt | 6 +- .../kotlin/app/repository/RoleRepository.kt | 9 + .../impl}/AccountRepositoryImpl.kt | 12 +- .../impl}/MenuRepositoryImpl.kt | 6 +- .../impl}/RoleRepositoryImpl.kt | 6 +- .../domain/menu => service}/MenuManager.kt | 12 +- .../app/service/account/AccountService.kt | 4 +- .../kotlin/app/service/auth/TokenService.kt | 16 +- .../kotlin/app/{util => utils}/CacheUtil.kt | 2 +- .../src/main/kotlin/app/utils/RedisUtil.kt | 835 ++++++++++++++++++ .../openapi/ApifoxUtil.kt} | 9 +- .../openapi/OpenApiSpecGenerator.kt | 2 +- .../main/kotlin/app/verticle/WebVerticle.kt | 6 +- .../resources/dbmigration/1.0__initial.sql | 29 +- .../dbmigration/model/1.0__initial.model.xml | 17 +- .../org/aikrai/vertx/config/AppConfigs.kt | 2 +- .../vertx/config/FrameworkConfigModule.kt | 2 +- 34 files changed, 1029 insertions(+), 125 deletions(-) create mode 100644 vertx-demo/src/main/kotlin/app/config/provider/RedisProvider.kt delete mode 100644 vertx-demo/src/main/kotlin/app/data/domain/role/RoleRepository.kt rename vertx-demo/src/main/kotlin/app/data/{domain/account/modle => dto/account}/AccountRoleAccessDTO.kt (93%) rename vertx-demo/src/main/kotlin/app/data/{domain/account/modle => dto/account}/AccountRoleDTO.kt (87%) rename vertx-demo/src/main/kotlin/app/data/{domain => dto}/account/LoginDTO.kt (66%) rename vertx-demo/src/main/kotlin/app/data/{domain/account/modle => dto/account}/LoginUser.kt (82%) rename vertx-demo/src/main/kotlin/app/data/{domain/menu/modle => dto/menu}/RouterVo.kt (100%) rename vertx-demo/src/main/kotlin/app/data/{domain/menu/modle => dto/menu}/TreeSelect.kt (95%) rename vertx-demo/src/main/kotlin/app/data/{domain/menu => emun}/MenuType.kt (64%) create mode 100644 vertx-demo/src/main/kotlin/app/data/emun/SexType.kt delete mode 100644 vertx-demo/src/main/kotlin/app/port/reids/RedisClient.kt rename vertx-demo/src/main/kotlin/app/{data/domain/account => repository}/AccountRepository.kt (71%) rename vertx-demo/src/main/kotlin/app/{data/domain/menu => repository}/MenuRepository.kt (73%) create mode 100644 vertx-demo/src/main/kotlin/app/repository/RoleRepository.kt rename vertx-demo/src/main/kotlin/app/{data/domain/account => repository/impl}/AccountRepositoryImpl.kt (93%) rename vertx-demo/src/main/kotlin/app/{data/domain/menu => repository/impl}/MenuRepositoryImpl.kt (78%) rename vertx-demo/src/main/kotlin/app/{data/domain/role => repository/impl}/RoleRepositoryImpl.kt (54%) rename vertx-demo/src/main/kotlin/app/{data/domain/menu => service}/MenuManager.kt (91%) rename vertx-demo/src/main/kotlin/app/{util => utils}/CacheUtil.kt (98%) create mode 100644 vertx-demo/src/main/kotlin/app/utils/RedisUtil.kt rename vertx-demo/src/main/kotlin/app/{port/aipfox/ApifoxClient.kt => utils/openapi/ApifoxUtil.kt} (94%) rename vertx-demo/src/main/kotlin/app/{util => utils}/openapi/OpenApiSpecGenerator.kt (99%) diff --git a/README.md b/README.md index ab5e2eb..528a6af 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +闲暇时编写,问题较多,仅供参考。 + # Vert.x 快速开发模板 这是一个基于 Vert.x 库的后端快速开发模板,采用 Kotlin 语言和协程实现高性能异步编程。本项目提供了完整的开发框架,能够帮助开发者快速搭建响应式、模块化、高性能的后端服务。 @@ -7,7 +9,7 @@ - **核心框架**: Vert.x 4.5.x (响应式、事件驱动框架) - **编程语言**: Kotlin 1.9.x (协程支持) - **依赖注入**: Google Guice 7.0.0 -- **数据库**: PostgreSQL (主), MySQL (可选) +- **数据库**: PostgreSQL, (不支持MySQL) - **缓存**: Redis - **认证**: JWT - **构建工具**: Gradle with Kotlin DSL diff --git a/vertx-demo/src/main/kotlin/app/config/InjectConfig.kt b/vertx-demo/src/main/kotlin/app/config/InjectConfig.kt index 2f3b324..e25e3d8 100644 --- a/vertx-demo/src/main/kotlin/app/config/InjectConfig.kt +++ b/vertx-demo/src/main/kotlin/app/config/InjectConfig.kt @@ -2,6 +2,7 @@ package app.config import app.config.provider.JWTAuthProvider import app.config.provider.DbPoolProvider +import app.config.provider.RedisProvider import cn.hutool.core.lang.Snowflake import cn.hutool.core.util.IdUtil import com.google.inject.AbstractModule @@ -10,6 +11,7 @@ import com.google.inject.Injector import com.google.inject.Singleton import io.vertx.core.Vertx import io.vertx.ext.auth.jwt.JWTAuth +import io.vertx.redis.client.Redis import io.vertx.sqlclient.Pool import io.vertx.sqlclient.SqlClient import kotlinx.coroutines.CoroutineScope @@ -41,6 +43,7 @@ class InjectorModule( bind(Snowflake::class.java).toInstance(IdUtil.getSnowflake()) + bind(Redis::class.java).toProvider(RedisProvider::class.java).`in`(Singleton::class.java) bind(Pool::class.java).toProvider(DbPoolProvider::class.java).`in`(Singleton::class.java) bind(SqlClient::class.java).to(Pool::class.java) diff --git a/vertx-demo/src/main/kotlin/app/config/provider/RedisProvider.kt b/vertx-demo/src/main/kotlin/app/config/provider/RedisProvider.kt new file mode 100644 index 0000000..097a7b1 --- /dev/null +++ b/vertx-demo/src/main/kotlin/app/config/provider/RedisProvider.kt @@ -0,0 +1,24 @@ +package app.config.provider + +import com.google.inject.Inject +import com.google.inject.Provider +import io.vertx.core.Vertx +import io.vertx.redis.client.Redis +import io.vertx.redis.client.RedisClientType +import io.vertx.redis.client.RedisOptions +import org.aikrai.vertx.config.RedisConfig + +class RedisProvider @Inject constructor( + private val vertx: Vertx, + private val redisConfig: RedisConfig +) : Provider { + override fun get(): Redis { + val options = RedisOptions() + .setType(RedisClientType.STANDALONE) + .addConnectionString("redis://${redisConfig.host}:${redisConfig.port}/${redisConfig.db}") + .setMaxPoolSize(redisConfig.poolSize) + .setMaxPoolWaiting(redisConfig.maxPoolWaiting) + redisConfig.password?.let { options.setPassword(it) } + return Redis.createClient(vertx, options) + } +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/controller/AuthController.kt b/vertx-demo/src/main/kotlin/app/controller/AuthController.kt index c69e23f..685ce97 100644 --- a/vertx-demo/src/main/kotlin/app/controller/AuthController.kt +++ b/vertx-demo/src/main/kotlin/app/controller/AuthController.kt @@ -1,6 +1,6 @@ package app.controller -import app.data.domain.account.LoginDTO +import app.data.dto.account.LoginDTO import app.service.account.AccountService import com.google.inject.Inject import io.vertx.ext.web.RoutingContext diff --git a/vertx-demo/src/main/kotlin/app/controller/Demo1Controller.kt b/vertx-demo/src/main/kotlin/app/controller/Demo1Controller.kt index 75838f4..5807da4 100644 --- a/vertx-demo/src/main/kotlin/app/controller/Demo1Controller.kt +++ b/vertx-demo/src/main/kotlin/app/controller/Demo1Controller.kt @@ -1,7 +1,7 @@ package app.controller import app.data.domain.account.Account -import app.data.domain.account.AccountRepository +import app.repository.AccountRepository import app.data.emun.Status import app.service.account.AccountService import com.google.inject.Inject diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt b/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt index 5f8a051..3663922 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt +++ b/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt @@ -1,5 +1,6 @@ package app.data.domain.account +import app.data.emun.SexType import app.data.emun.Status import org.aikrai.vertx.db.annotation.* import org.aikrai.vertx.jackson.JsonUtil @@ -13,21 +14,47 @@ class Account : BaseEntity() { @TableFieldComment("用户ID") var userId: Long = 0L - @TableField("user_name") - var userName: String? = "" + @TableFieldComment("部门ID") + var deptId: Long? = 0L + @TableField(length = 30) + @TableFieldComment("用户账号") + var userName: String = "" + + @TableField(length = 30) + @TableFieldComment("用户昵称") + var nickName: String = "" + + @TableField(length = 2) + @TableFieldComment("用户类型") var userType: String? = "" + @TableField(length = 50) + @TableFieldComment("用户邮箱") var email: String? = "" - var phone: String? = "" + @TableField(length = 11) + @TableFieldComment("手机号码") + var phonenumber: String? = "" + @TableField(length = 1) + @TableFieldComment("用户性别(0男 1女 2未知)") + var sex: SexType? = SexType.UNKNOWN + + @TableField(length = 100) + @TableFieldComment("头像地址") var avatar: String? = null + @TableField(length = 100) + @TableFieldComment("密码") var password: String? = null + @TableField(length = 1) + @TableFieldComment("帐号状态(0正常 1停用)") var status: Status? = Status.ACTIVE + @TableField(length = 1) + @TableFieldComment("删除标志(0代表存在 2代表删除)") var delFlag: Char? = null var loginIp: String? = null diff --git a/vertx-demo/src/main/kotlin/app/data/domain/role/RoleRepository.kt b/vertx-demo/src/main/kotlin/app/data/domain/role/RoleRepository.kt deleted file mode 100644 index 4f1d5bf..0000000 --- a/vertx-demo/src/main/kotlin/app/data/domain/role/RoleRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.data.domain.role - -import com.google.inject.ImplementedBy -import org.aikrai.vertx.db.wrapper.Repository - -@ImplementedBy(RoleRepositoryImpl::class) -interface RoleRepository : Repository diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/modle/AccountRoleAccessDTO.kt b/vertx-demo/src/main/kotlin/app/data/dto/account/AccountRoleAccessDTO.kt similarity index 93% rename from vertx-demo/src/main/kotlin/app/data/domain/account/modle/AccountRoleAccessDTO.kt rename to vertx-demo/src/main/kotlin/app/data/dto/account/AccountRoleAccessDTO.kt index 905ef00..7752415 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/modle/AccountRoleAccessDTO.kt +++ b/vertx-demo/src/main/kotlin/app/data/dto/account/AccountRoleAccessDTO.kt @@ -1,4 +1,4 @@ -package app.data.domain.account.modle +package app.data.dto.account import app.data.domain.account.Account import app.data.domain.menu.Menu diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/modle/AccountRoleDTO.kt b/vertx-demo/src/main/kotlin/app/data/dto/account/AccountRoleDTO.kt similarity index 87% rename from vertx-demo/src/main/kotlin/app/data/domain/account/modle/AccountRoleDTO.kt rename to vertx-demo/src/main/kotlin/app/data/dto/account/AccountRoleDTO.kt index aa9a36c..44e1f20 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/modle/AccountRoleDTO.kt +++ b/vertx-demo/src/main/kotlin/app/data/dto/account/AccountRoleDTO.kt @@ -1,4 +1,4 @@ -package app.data.domain.account.modle +package app.data.dto.account import app.data.domain.role.Role diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/LoginDTO.kt b/vertx-demo/src/main/kotlin/app/data/dto/account/LoginDTO.kt similarity index 66% rename from vertx-demo/src/main/kotlin/app/data/domain/account/LoginDTO.kt rename to vertx-demo/src/main/kotlin/app/data/dto/account/LoginDTO.kt index 58433bf..ded5a76 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/LoginDTO.kt +++ b/vertx-demo/src/main/kotlin/app/data/dto/account/LoginDTO.kt @@ -1,6 +1,6 @@ -package app.data.domain.account +package app.data.dto.account data class LoginDTO( var username: String, var password: String -) +) \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/modle/LoginUser.kt b/vertx-demo/src/main/kotlin/app/data/dto/account/LoginUser.kt similarity index 82% rename from vertx-demo/src/main/kotlin/app/data/domain/account/modle/LoginUser.kt rename to vertx-demo/src/main/kotlin/app/data/dto/account/LoginUser.kt index 6c8da93..b6da527 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/modle/LoginUser.kt +++ b/vertx-demo/src/main/kotlin/app/data/dto/account/LoginUser.kt @@ -1,4 +1,4 @@ -package app.base.domain.auth.modle +package app.data.dto.account class LoginUser { var accountId: Long = 0L @@ -7,4 +7,4 @@ class LoginUser { var expireTime: Long = 0L var ipaddr: String = "" var client: String = "" -} +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/domain/menu/modle/RouterVo.kt b/vertx-demo/src/main/kotlin/app/data/dto/menu/RouterVo.kt similarity index 100% rename from vertx-demo/src/main/kotlin/app/data/domain/menu/modle/RouterVo.kt rename to vertx-demo/src/main/kotlin/app/data/dto/menu/RouterVo.kt diff --git a/vertx-demo/src/main/kotlin/app/data/domain/menu/modle/TreeSelect.kt b/vertx-demo/src/main/kotlin/app/data/dto/menu/TreeSelect.kt similarity index 95% rename from vertx-demo/src/main/kotlin/app/data/domain/menu/modle/TreeSelect.kt rename to vertx-demo/src/main/kotlin/app/data/dto/menu/TreeSelect.kt index 5a6daff..822ff37 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/menu/modle/TreeSelect.kt +++ b/vertx-demo/src/main/kotlin/app/data/dto/menu/TreeSelect.kt @@ -1,4 +1,4 @@ -package app.data.domain.menu.modle +package app.data.dto.menu import app.data.domain.menu.Menu diff --git a/vertx-demo/src/main/kotlin/app/data/domain/menu/MenuType.kt b/vertx-demo/src/main/kotlin/app/data/emun/MenuType.kt similarity index 64% rename from vertx-demo/src/main/kotlin/app/data/domain/menu/MenuType.kt rename to vertx-demo/src/main/kotlin/app/data/emun/MenuType.kt index b85e0b0..0d5acf9 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/menu/MenuType.kt +++ b/vertx-demo/src/main/kotlin/app/data/emun/MenuType.kt @@ -1,4 +1,4 @@ -package app.base.domain.auth.menu +package app.data.emun enum class MenuType(val desc: String) { M("目录"), @@ -8,7 +8,7 @@ enum class MenuType(val desc: String) { companion object { fun parse(value: String?): MenuType? { if (value.isNullOrBlank()) return null - return MenuType.values().find { it.name == value || it.desc == value } + return MenuType.entries.find { it.name == value || it.desc == value } } } -} +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/emun/SexType.kt b/vertx-demo/src/main/kotlin/app/data/emun/SexType.kt new file mode 100644 index 0000000..7a61636 --- /dev/null +++ b/vertx-demo/src/main/kotlin/app/data/emun/SexType.kt @@ -0,0 +1,28 @@ +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 SexType(private val code: Int, private val description: String) { + MALE(0, "男"), + FEMALE(1, "女"), + UNKNOWN(2, "未知"); + + @JsonValue + @EnumValue + fun getCode(): Int { + return code + } + + override fun toString(): String { + return description + } + + companion object { + @JsonCreator + fun parse(code: Int): SexType? { + return SexType.entries.find { it.code == code } + } + } +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/port/reids/RedisClient.kt b/vertx-demo/src/main/kotlin/app/port/reids/RedisClient.kt deleted file mode 100644 index fec3ae5..0000000 --- a/vertx-demo/src/main/kotlin/app/port/reids/RedisClient.kt +++ /dev/null @@ -1,46 +0,0 @@ -package app.port.reids - -import com.google.inject.Inject -import com.google.inject.Singleton -import io.vertx.core.Vertx -import io.vertx.kotlin.coroutines.coAwait -import io.vertx.redis.client.* -import io.github.oshai.kotlinlogging.KotlinLogging -import org.aikrai.vertx.config.RedisConfig - -@Singleton -class RedisClient @Inject constructor( - vertx: Vertx, - redisConfig: RedisConfig -) { - private val logger = KotlinLogging.logger { } - - private var redisClient = Redis.createClient( - vertx, - RedisOptions() - .setType(RedisClientType.STANDALONE) - .addConnectionString("redis://${redisConfig.host}:${redisConfig.port}/${redisConfig.db}") - .setPassword(redisConfig.pass ?: "") - .setMaxPoolSize(redisConfig.poolSize) - .setMaxPoolWaiting(redisConfig.maxPoolWaiting) - ) - - // EX秒,PX毫秒 - suspend fun set(key: String, value: String, expireSeconds: Int) { - redisClient.send(Request.cmd(Command.SET, key, value, "EX", expireSeconds)) - } - - suspend fun get(key: String): String? { - val res = redisClient.send(Request.cmd(Command.GET, key)).coAwait() - return res?.toString() - } - - suspend fun incr(key: String): Int { - val res = redisClient.send(Request.cmd(Command.INCR, key)).coAwait() - return res?.toInteger() ?: 0 - } - - fun expire(key: String, expire: String) { - redisClient.send(Request.cmd(Command.EXPIRE, key, expire)) - } -} diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/AccountRepository.kt b/vertx-demo/src/main/kotlin/app/repository/AccountRepository.kt similarity index 71% rename from vertx-demo/src/main/kotlin/app/data/domain/account/AccountRepository.kt rename to vertx-demo/src/main/kotlin/app/repository/AccountRepository.kt index e747f0f..8d864c5 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/AccountRepository.kt +++ b/vertx-demo/src/main/kotlin/app/repository/AccountRepository.kt @@ -1,7 +1,9 @@ -package app.data.domain.account +package app.repository -import app.data.domain.account.modle.AccountRoleAccessDTO -import app.data.domain.account.modle.AccountRoleDTO +import app.data.domain.account.Account +import app.repository.impl.AccountRepositoryImpl +import app.data.dto.account.AccountRoleAccessDTO +import app.data.dto.account.AccountRoleDTO import com.google.inject.ImplementedBy import org.aikrai.vertx.db.wrapper.Repository @@ -14,4 +16,4 @@ interface AccountRepository : Repository { suspend fun getAccountRole(id: Long): AccountRoleDTO? suspend fun bindRoles(id: Long, roles: List) suspend fun removeAllRole(id: Long): Int -} +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/domain/menu/MenuRepository.kt b/vertx-demo/src/main/kotlin/app/repository/MenuRepository.kt similarity index 73% rename from vertx-demo/src/main/kotlin/app/data/domain/menu/MenuRepository.kt rename to vertx-demo/src/main/kotlin/app/repository/MenuRepository.kt index 3eed7b5..47a7999 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/menu/MenuRepository.kt +++ b/vertx-demo/src/main/kotlin/app/repository/MenuRepository.kt @@ -1,9 +1,11 @@ -package app.data.domain.menu +package app.repository +import app.data.domain.menu.Menu +import app.repository.impl.MenuRepositoryImpl import com.google.inject.ImplementedBy import org.aikrai.vertx.db.wrapper.Repository @ImplementedBy(MenuRepositoryImpl::class) interface MenuRepository : Repository { suspend fun list(name: String? = null, accountId: Long? = null, roleId: Long? = null): List -} +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/repository/RoleRepository.kt b/vertx-demo/src/main/kotlin/app/repository/RoleRepository.kt new file mode 100644 index 0000000..9f58505 --- /dev/null +++ b/vertx-demo/src/main/kotlin/app/repository/RoleRepository.kt @@ -0,0 +1,9 @@ +package app.repository + +import app.data.domain.role.Role +import app.repository.impl.RoleRepositoryImpl +import com.google.inject.ImplementedBy +import org.aikrai.vertx.db.wrapper.Repository + +@ImplementedBy(RoleRepositoryImpl::class) +interface RoleRepository : Repository \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/AccountRepositoryImpl.kt b/vertx-demo/src/main/kotlin/app/repository/impl/AccountRepositoryImpl.kt similarity index 93% rename from vertx-demo/src/main/kotlin/app/data/domain/account/AccountRepositoryImpl.kt rename to vertx-demo/src/main/kotlin/app/repository/impl/AccountRepositoryImpl.kt index a1a269e..107de74 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/AccountRepositoryImpl.kt +++ b/vertx-demo/src/main/kotlin/app/repository/impl/AccountRepositoryImpl.kt @@ -1,7 +1,9 @@ -package app.data.domain.account +package app.repository.impl -import app.data.domain.account.modle.AccountRoleAccessDTO -import app.data.domain.account.modle.AccountRoleDTO +import app.data.domain.account.Account +import app.data.dto.account.AccountRoleAccessDTO +import app.data.dto.account.AccountRoleDTO +import app.repository.AccountRepository import com.google.inject.Inject import io.vertx.sqlclient.SqlClient import org.aikrai.vertx.db.wrapper.RepositoryImpl @@ -16,7 +18,7 @@ class AccountRepositoryImpl @Inject constructor( ): List { return queryBuilder() .eq(!userName.isNullOrBlank(), Account::userName, userName) - .eq(!phone.isNullOrBlank(), Account::phone, phone) + .eq(!phone.isNullOrBlank(), Account::phonenumber, phone) .getList() } @@ -101,4 +103,4 @@ class AccountRepositoryImpl @Inject constructor( val sql = "DELETE FROM account_role WHERE account_id = #{$id}" return execute(sql) } -} +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/domain/menu/MenuRepositoryImpl.kt b/vertx-demo/src/main/kotlin/app/repository/impl/MenuRepositoryImpl.kt similarity index 78% rename from vertx-demo/src/main/kotlin/app/data/domain/menu/MenuRepositoryImpl.kt rename to vertx-demo/src/main/kotlin/app/repository/impl/MenuRepositoryImpl.kt index 8e16acf..88f46d5 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/menu/MenuRepositoryImpl.kt +++ b/vertx-demo/src/main/kotlin/app/repository/impl/MenuRepositoryImpl.kt @@ -1,5 +1,7 @@ -package app.data.domain.menu +package app.repository.impl +import app.data.domain.menu.Menu +import app.repository.MenuRepository import com.google.inject.Inject import io.vertx.sqlclient.SqlClient import org.aikrai.vertx.db.wrapper.RepositoryImpl @@ -11,4 +13,4 @@ class MenuRepositoryImpl @Inject constructor( override suspend fun list(name: String?, accountId: Long?, roleId: Long?): List { return emptyList() } -} +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/domain/role/RoleRepositoryImpl.kt b/vertx-demo/src/main/kotlin/app/repository/impl/RoleRepositoryImpl.kt similarity index 54% rename from vertx-demo/src/main/kotlin/app/data/domain/role/RoleRepositoryImpl.kt rename to vertx-demo/src/main/kotlin/app/repository/impl/RoleRepositoryImpl.kt index 761bb62..f47a265 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/role/RoleRepositoryImpl.kt +++ b/vertx-demo/src/main/kotlin/app/repository/impl/RoleRepositoryImpl.kt @@ -1,9 +1,11 @@ -package app.data.domain.role +package app.repository.impl +import app.data.domain.role.Role +import app.repository.RoleRepository import com.google.inject.Inject import io.vertx.sqlclient.SqlClient import org.aikrai.vertx.db.wrapper.RepositoryImpl class RoleRepositoryImpl @Inject constructor( sqlClient: SqlClient -) : RepositoryImpl(sqlClient), RoleRepository +) : RepositoryImpl(sqlClient), RoleRepository \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/domain/menu/MenuManager.kt b/vertx-demo/src/main/kotlin/app/service/MenuManager.kt similarity index 91% rename from vertx-demo/src/main/kotlin/app/data/domain/menu/MenuManager.kt rename to vertx-demo/src/main/kotlin/app/service/MenuManager.kt index 6b6a12a..8211e2d 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/menu/MenuManager.kt +++ b/vertx-demo/src/main/kotlin/app/service/MenuManager.kt @@ -1,6 +1,8 @@ -package app.data.domain.menu +package app.service import app.data.domain.account.Account +import app.data.domain.menu.Menu +import app.repository.MenuRepository import com.google.inject.Inject import com.google.inject.Singleton import io.vertx.ext.auth.User @@ -47,7 +49,7 @@ class MenuManager @Inject constructor( perms: String ) { if (menuRepository.list(menuName).isNotEmpty()) { - throw Meta.error("MenuNameConflict", "菜单名称已存在") + throw Meta.Companion.error("MenuNameConflict", "菜单名称已存在") } val menu = Menu().apply { this.menuName = menuName @@ -73,10 +75,10 @@ class MenuManager @Inject constructor( visible: String?, perms: String? ) { - val menu = menuRepository.get(menuId) ?: throw Meta.notFound("MenuNotFound", "菜单不存在") + val menu = menuRepository.get(menuId) ?: throw Meta.Companion.notFound("MenuNotFound", "菜单不存在") if (menuName != null && menuName != menu.menuName && menuRepository.list(menuName).isNotEmpty()) { - throw Meta.error("MenuNameConflict", "菜单名称已存在") + throw Meta.Companion.error("MenuNameConflict", "菜单名称已存在") } menu.apply { @@ -147,4 +149,4 @@ class MenuManager @Inject constructor( return getChildList(list, t).isNotEmpty() } } -} +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/service/account/AccountService.kt b/vertx-demo/src/main/kotlin/app/service/account/AccountService.kt index 0bcffb3..5538ac6 100644 --- a/vertx-demo/src/main/kotlin/app/service/account/AccountService.kt +++ b/vertx-demo/src/main/kotlin/app/service/account/AccountService.kt @@ -1,8 +1,8 @@ package app.service.account import app.data.domain.account.Account -import app.data.domain.account.AccountRepository -import app.data.domain.account.LoginDTO +import app.repository.AccountRepository +import app.data.dto.account.LoginDTO import app.service.auth.TokenService import cn.hutool.core.lang.Snowflake import cn.hutool.crypto.SecureUtil diff --git a/vertx-demo/src/main/kotlin/app/service/auth/TokenService.kt b/vertx-demo/src/main/kotlin/app/service/auth/TokenService.kt index 49f321b..e7ef2e4 100644 --- a/vertx-demo/src/main/kotlin/app/service/auth/TokenService.kt +++ b/vertx-demo/src/main/kotlin/app/service/auth/TokenService.kt @@ -1,7 +1,7 @@ package app.service.auth -import app.data.domain.account.AccountRepository -import app.port.reids.RedisClient +import app.repository.AccountRepository +import app.utils.RedisUtil import cn.hutool.core.util.IdUtil import com.google.inject.Inject import com.google.inject.Singleton @@ -14,20 +14,24 @@ import io.vertx.ext.auth.jwt.JWTAuth import io.vertx.ext.web.RoutingContext import io.vertx.kotlin.coroutines.coAwait import io.github.oshai.kotlinlogging.KotlinLogging +import io.vertx.redis.client.Redis import org.aikrai.vertx.auth.AuthUser import org.aikrai.vertx.constant.CacheConstants import org.aikrai.vertx.constant.Constants import org.aikrai.vertx.jackson.JsonUtil import org.aikrai.vertx.utlis.Meta +import java.util.concurrent.TimeUnit @Singleton class TokenService @Inject constructor( + redis: Redis, private val jwtAuth: JWTAuth, - private val redisClient: RedisClient, private val accountRepository: AccountRepository, ) { private val logger = KotlinLogging.logger { } - private val expireSeconds = 60 * 60 * 24 * 7 + private val expireSeconds = 60L * 60 * 24 * 7 + private val redisUtil = RedisUtil(redis) + suspend fun getLoginUser(ctx: RoutingContext): AuthUser { val request = ctx.request() @@ -38,7 +42,7 @@ class TokenService @Inject constructor( val token = authorization.substring(6) val user = parseToken(token) ?: throw Meta.unauthorized("token") val userToken = user.principal().getString(Constants.LOGIN_USER_KEY) ?: throw Meta.unauthorized("token") - val authInfoStr = redisClient.get(CacheConstants.LOGIN_TOKEN_KEY + userToken) ?: throw Meta.unauthorized("token") + val authInfoStr = redisUtil.getObject(CacheConstants.LOGIN_TOKEN_KEY + userToken) ?: throw Meta.unauthorized("token") return JsonUtil.parseObject(authInfoStr, AuthUser::class.java) } @@ -48,7 +52,7 @@ class TokenService @Inject constructor( val user = userInfo?.account ?: throw Meta.notFound("AccountNotFound", "账号不存在") val authInfo = AuthUser(userInfo.account.userId, token, JsonUtil.toJsonObject(user), userInfo.rolesArr.toSet(), userInfo.accessArr.toSet(), ip, client) val authInfoStr = JsonUtil.toJsonStr(authInfo) - redisClient.set(CacheConstants.LOGIN_TOKEN_KEY + token, authInfoStr, expireSeconds) + redisUtil.setObject(CacheConstants.LOGIN_TOKEN_KEY + token, authInfoStr, expireSeconds, TimeUnit.SECONDS) return genToken(mapOf(Constants.LOGIN_USER_KEY to token)) } diff --git a/vertx-demo/src/main/kotlin/app/util/CacheUtil.kt b/vertx-demo/src/main/kotlin/app/utils/CacheUtil.kt similarity index 98% rename from vertx-demo/src/main/kotlin/app/util/CacheUtil.kt rename to vertx-demo/src/main/kotlin/app/utils/CacheUtil.kt index 24b8b2e..7e857b2 100644 --- a/vertx-demo/src/main/kotlin/app/util/CacheUtil.kt +++ b/vertx-demo/src/main/kotlin/app/utils/CacheUtil.kt @@ -1,4 +1,4 @@ -package app.util +package app.utils import com.github.benmanes.caffeine.cache.Caffeine import com.google.inject.Singleton diff --git a/vertx-demo/src/main/kotlin/app/utils/RedisUtil.kt b/vertx-demo/src/main/kotlin/app/utils/RedisUtil.kt new file mode 100644 index 0000000..792e5b3 --- /dev/null +++ b/vertx-demo/src/main/kotlin/app/utils/RedisUtil.kt @@ -0,0 +1,835 @@ +package app.utils + +import io.vertx.kotlin.coroutines.coAwait +import io.vertx.redis.client.Command +import io.vertx.redis.client.Redis +import io.vertx.redis.client.Request +import java.util.concurrent.TimeUnit + +class RedisUtil constructor(private val redis: Redis) { + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + suspend fun setObject(key: String, value: T): Boolean { + val request = Request.cmd(Command.SET) + .arg(key) + .arg(value.toString()) + .arg("KEEPTTL") + val response = redis.send(request).coAwait() + return response?.toString() == "OK" + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + suspend fun setObject(key: String, value: T, timeout: Long, timeUnit: TimeUnit): Boolean { + val expireSeconds = timeUnit.toSeconds(timeout) + val request = Request.cmd(Command.SET, key, value.toString(), "EX", expireSeconds.toString()) + val response = redis.send(request).coAwait() + return response?.toString() == "OK" + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + suspend fun expire(key: String, timeout: Long): Boolean { + return expire(key, timeout, TimeUnit.SECONDS) + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + suspend fun expire(key: String, timeout: Long, unit: TimeUnit): Boolean { + val expireSeconds = unit.toSeconds(timeout) + val response = redis.send(Request.cmd(Command.EXPIRE, key, expireSeconds.toString())).coAwait() + return response?.toLong() == 1L + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + suspend fun getExpire(key: String): Long? { + val response = redis.send(Request.cmd(Command.TTL, key)).coAwait() + val ttl = response?.toLong() + return if (ttl == -1L || ttl == -2L) null else ttl + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + suspend fun hasKey(key: String): Boolean { + val response = redis.send(Request.cmd(Command.EXISTS, key)).coAwait() + return (response?.toLong() ?: 0) > 0 + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + suspend fun getObject(key: String): T? { + val response = redis.send(Request.cmd(Command.GET, key)).coAwait() + return response?.toString() as? T + } + + /** + * 删除单个对象 + * + * @param key + */ + suspend fun deleteObject(key: String): Boolean { + val response = redis.send(Request.cmd(Command.DEL, key)).coAwait() + return response?.toLong() == 1L + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + suspend fun deleteObject(collection: Collection): Boolean { + if (collection.isEmpty()) return false + val response = redis.send(Request.cmd(Command.DEL, *collection.toTypedArray())).coAwait() + return (response?.toLong() ?: 0) > 0 + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + suspend fun setList(key: String, dataList: List): Long { + val args = mutableListOf().apply { + add(key) + dataList.forEach { add(it.toString()) } + } + val response = redis.send(Request.cmd(Command.RPUSH, *args.toTypedArray())).coAwait() + return response?.toLong() ?: 0 + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + suspend fun getList(key: String): List? { + val response = redis.send(Request.cmd(Command.LRANGE, key, "0", "-1")).coAwait() + return response?.map { it.toString() as T }?.toList() + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + suspend fun setSet(key: String, dataSet: Set): Long { + val args = mutableListOf().apply { + add(key) + dataSet.forEach { add(it.toString()) } + } + val response = redis.send(Request.cmd(Command.SADD, *args.toTypedArray())).coAwait() + return response?.toLong() ?: 0 + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + suspend fun getSet(key: String): Set? { + val response = redis.send(Request.cmd(Command.SMEMBERS, key)).coAwait() + return response?.map { it.toString() as T }?.toSet() + } + + /** + * 向Set中添加一个或多个元素 + * + * @param key 缓存键值 + * @param values 要添加的元素 + * @return 成功添加的元素数量 + */ + suspend fun sAdd(key: String, vararg values: T): Long { + val request = Request.cmd(Command.SADD) + .arg(key) + + // 逐个添加参数 + values.forEach { + request.arg(it.toString()) + } + + val response = redis.send(request).coAwait() + return response?.toLong() ?: 0 + } + + /** + * 向Set中添加一个或多个元素,并设置过期时间 + * + * @param key 缓存键值 + * @param value 要添加的元素 + * @param timeout 过期时间 + * @param timeUnit 时间单位 + * @return 是否成功添加元素 + */ + suspend fun sAdd(key: String, value: T, timeout: Long, timeUnit: TimeUnit): Boolean { + val added = sAdd(key, value) > 0 + if (added) { + expire(key, timeout, timeUnit) + } + return added + } + + /** + * 从Set中移除一个或多个元素 + * + * @param key 缓存键值 + * @param values 要移除的元素 + * @return 成功移除的元素数量 + */ + suspend fun sRemove(key: String, vararg values: T): Long { + val args = mutableListOf().apply { + add(key) + values.forEach { add(it.toString()) } + } + val response = redis.send(Request.cmd(Command.SREM, *args.toTypedArray())).coAwait() + return response?.toLong() ?: 0 + } + + /** + * 获取Set中的所有成员 + * + * @param key 缓存键值 + * @return Set中的所有成员 + */ + suspend fun sMembers(key: String): List { + val response = redis.send(Request.cmd(Command.SMEMBERS, key)).coAwait() + return response?.map { it.toString() } ?: emptyList() + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + suspend fun setMap(key: String, dataMap: Map): Boolean { + if (dataMap.isEmpty()) return false + val args = mutableListOf().apply { + add(key) + dataMap.forEach { (k, v) -> + add(k) + add(v.toString()) + } + } + val response = redis.send(Request.cmd(Command.HMSET, *args.toTypedArray())).coAwait() + return response?.toString() == "OK" + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + suspend fun getMap(key: String): Map? { + val response = redis.send(Request.cmd(Command.HGETALL, key)).coAwait() + if (response == null || response.size() == 0) return null + + val map = mutableMapOf() +// for (i in 0 until response.size() step 2) { +// val k = response.get(i).toString() +// val v = response.get(i + 1).toString() as T +// map[k] = v +// } + for (item in response) { + val list = item.toMutableList() + val k = list[0].toString() + val v = list[1].toString() as T + map[k] = v + } + return map + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + suspend fun setMapValue(key: String, hKey: String, value: T): Boolean { + val response = redis.send(Request.cmd(Command.HSET, key, hKey, value.toString())).coAwait() + return response?.toLong() == 1L + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + suspend fun getMapValue(key: String, hKey: String): T? { + val response = redis.send(Request.cmd(Command.HGET, key, hKey)).coAwait() + return response?.toString() as? T + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + suspend fun getMultiMapValue(key: String, hKeys: Collection): List? { + val args = mutableListOf().apply { + add(key) + addAll(hKeys) + } + val response = redis.send(Request.cmd(Command.HMGET, *args.toTypedArray())).coAwait() + return response?.map { if (it == null) null else it.toString() as T } + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + suspend fun deleteMapValue(key: String, hKey: String): Boolean { + val response = redis.send(Request.cmd(Command.HDEL, key, hKey)).coAwait() + return response?.toLong() == 1L + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + suspend fun keys(pattern: String): List? { + val response = redis.send(Request.cmd(Command.KEYS, pattern)).coAwait() + return response?.map { it.toString() } + } + + /** + * 自增操作 + * + * @param key Redis键 + * @return 自增后的值 + */ + suspend fun incr(key: String): Long { + val response = redis.send(Request.cmd(Command.INCR, key)).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 自增指定步长 + * + * @param key Redis键 + * @param increment 步长 + * @return 自增后的值 + */ + suspend fun incrBy(key: String, increment: Long): Long { + val response = redis.send(Request.cmd(Command.INCRBY, key, increment.toString())).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 自减操作 + * + * @param key Redis键 + * @return 自减后的值 + */ + suspend fun decr(key: String): Long { + val response = redis.send(Request.cmd(Command.DECR, key)).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 自减指定步长 + * + * @param key Redis键 + * @param decrement 步长 + * @return 自减后的值 + */ + suspend fun decrBy(key: String, decrement: Long): Long { + val response = redis.send(Request.cmd(Command.DECRBY, key, decrement.toString())).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 浮点数自增 + * + * @param key Redis键 + * @param increment 增量 + * @return 自增后的值 + */ + suspend fun incrByFloat(key: String, increment: Double): Double { + val response = redis.send(Request.cmd(Command.INCRBYFLOAT, key, increment.toString())).coAwait() + return response?.toDouble() ?: 0.0 + } + + /** + * 设置键值并返回旧值 + * + * @param key Redis键 + * @param value 新值 + * @return 旧值 + */ + suspend fun getSet(key: String, value: T): T? { + val response = redis.send(Request.cmd(Command.GETSET, key, value.toString())).coAwait() + return response?.toString() as? T + } + + /** + * 设置键值(仅当键不存在时) + * + * @param key Redis键 + * @param value 值 + * @return 是否设置成功 + */ + suspend fun setIfAbsent(key: String, value: T): Boolean { + val response = redis.send(Request.cmd(Command.SETNX, key, value.toString())).coAwait() + return response?.toLong() == 1L + } + + /** + * 获取字符串长度 + * + * @param key Redis键 + * @return 字符串长度 + */ + suspend fun strlen(key: String): Long { + val response = redis.send(Request.cmd(Command.STRLEN, key)).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 追加字符串 + * + * @param key Redis键 + * @param value 追加的值 + * @return 追加后的字符串长度 + */ + suspend fun append(key: String, value: String): Long { + val response = redis.send(Request.cmd(Command.APPEND, key, value)).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 获取子字符串 + * + * @param key Redis键 + * @param start 开始位置 + * @param end 结束位置 + * @return 子字符串 + */ + suspend fun getRange(key: String, start: Long, end: Long): String? { + val response = redis.send(Request.cmd(Command.GETRANGE, key, start.toString(), end.toString())).coAwait() + return response?.toString() + } + + /** + * 设置子字符串 + * + * @param key Redis键 + * @param offset 偏移量 + * @param value 值 + * @return 修改后的字符串长度 + */ + suspend fun setRange(key: String, offset: Long, value: String): Long { + val response = redis.send(Request.cmd(Command.SETRANGE, key, offset.toString(), value)).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 设置多个键值对 + * + * @param keyValues 键值对(key1, value1, key2, value2...) + * @return 是否成功 + */ + suspend fun mset(vararg keyValues: String): Boolean { + if (keyValues.size % 2 != 0) throw IllegalArgumentException("参数数量必须为偶数") + val response = redis.send(Request.cmd(Command.MSET, *keyValues)).coAwait() + return response?.toString() == "OK" + } + + /** + * 获取多个键的值 + * + * @param keys 键集合 + * @return 值列表 + */ + suspend fun mget(vararg keys: String): List { + val response = redis.send(Request.cmd(Command.MGET, *keys)).coAwait() + return response?.map { it?.toString() } ?: emptyList() + } + + /** + * 向有序集合添加一个或多个成员,或者更新已存在成员的分数 + * + * @param key Redis键 + * @param score 分数 + * @param member 成员 + * @return 成功添加的新成员的数量,不包括那些被更新的、已经存在的成员 + */ + suspend fun zadd(key: String, score: Double, member: String): Long { + val request = Request.cmd(Command.ZADD) + .arg(key) + .arg(score.toString()) + .arg(member) + + val response = redis.send(request).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 为有序集合的成员增加分数 + * + * @param key Redis键 + * @param increment 增量分数 + * @param member 成员 + * @return 增加后的分数 + */ + suspend fun zincrby(key: String, increment: Double, member: String): Double { + val request = Request.cmd(Command.ZINCRBY) + .arg(key) + .arg(increment.toString()) + .arg(member) + + val response = redis.send(request).coAwait() + return response?.toDouble() ?: 0.0 + } + + /** + * 获取有序集合中指定成员的分数 + * + * @param key Redis键 + * @param member 成员 + * @return 分数,如果成员不存在或键不存在则返回null + */ + suspend fun zscore(key: String, member: String): Double? { + val request = Request.cmd(Command.ZSCORE) + .arg(key) + .arg(member) + + val response = redis.send(request).coAwait() + return response?.toDouble() + } + + /** + * 获取有序集合中指定区间的成员,按分数从高到低排序 + * + * @param key Redis键 + * @param start 开始位置 + * @param stop 结束位置 + * @param withScores 是否返回分数 + * @return 指定区间的成员列表,如果withScores为true,则返回成员和分数的交替列表 + */ + suspend fun zrevrange(key: String, start: Long, stop: Long, withScores: Boolean = false): List { + // 使用可变参数列表而不是数组,避免类型转换问题 + val request = if (withScores) { + Request.cmd(Command.ZREVRANGE) + .arg(key) + .arg(start.toString()) + .arg(stop.toString()) + .arg("WITHSCORES") + } else { + Request.cmd(Command.ZREVRANGE) + .arg(key) + .arg(start.toString()) + .arg(stop.toString()) + } + + val response = redis.send(request).coAwait() + return response?.map { it.toString() } ?: emptyList() + } + + /** + * 获取有序集合中所有成员的分数总和 + * + * @param key Redis键 + * @return 所有成员的分数总和 + */ + suspend fun zsumScores(key: String): Double { + // 首先检查键是否存在 + if (!hasKey(key)) return 0.0 + + val request = Request.cmd(Command.ZRANGE) + .arg(key) + .arg("0") + .arg("-1") + .arg("WITHSCORES") + + val response = redis.send(request).coAwait() + if (response == null || response.size() == 0) return 0.0 + + var sum = 0.0 + for (item in response) { + sum += item.toMutableList()[1]?.toString()?.toDoubleOrNull() ?: 0.0 + } + return sum + } + + /** + * 使用 SCAN 命令获取匹配指定模式的所有键 + * + * @param pattern 匹配模式(例如:content:clicks:*) + * @return 匹配模式的键列表 + */ + suspend fun scan(pattern: String): List { + val keys = mutableListOf() + var cursor = "0" + + do { + val request = Request.cmd(Command.SCAN) + .arg(cursor) + .arg("MATCH") + .arg(pattern) + .arg("COUNT") + .arg("100") // 每次迭代返回的键数量 + + val response = redis.send(request).coAwait() + if (response != null && response.size() >= 2) { + cursor = response.get(0).toString() + + // 从索引1处获取键列表 + val scanKeys = response.get(1) + for (i in 0 until scanKeys.size()) { + keys.add(scanKeys.get(i).toString()) + } + } else { + break + } + } while (cursor != "0") + + return keys + } + + /** + * 计算多个有序集合的并集,并将结果存储在新的键中 + * + * @param destKey 目标键,存储计算结果 + * @param keys 要计算并集的有序集合键列表 + * @param weights 各有序集的权重(可选) + * @param aggregate 结果集的聚合方式(可选,默认为 SUM) + * @return 目标键中的元素数量 + */ + suspend fun zunionstore( + destKey: String, + keys: Array, + weights: DoubleArray? = null, + aggregate: String = "SUM" + ): Long { + if (keys.isEmpty()) return 0 + + val request = Request.cmd(Command.ZUNIONSTORE) + .arg(destKey) + .arg(keys.size.toString()) + + // 添加源集合键 + keys.forEach { request.arg(it) } + + // 添加权重(如果指定) + if (weights != null && weights.size == keys.size) { + request.arg("WEIGHTS") + weights.forEach { request.arg(it.toString()) } + } + + // 添加聚合方式 + if (aggregate in listOf("SUM", "MIN", "MAX")) { + request.arg("AGGREGATE") + request.arg(aggregate) + } + + val response = redis.send(request).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 计算多个有序集合的并集,使用相同的权重 + * + * @param destKey 目标键,存储计算结果 + * @param keys 要计算并集的有序集合键列表 + * @return 目标键中的元素数量 + */ + suspend fun zunionstoreWithEqualWeights(destKey: String, keys: Array): Long { + val weights = DoubleArray(keys.size) { 1.0 } + return zunionstore(destKey, keys, weights) + } + + /** + * 计算多个有序集合的并集,对结果取最大值 + * + * @param destKey 目标键,存储计算结果 + * @param keys 要计算并集的有序集合键列表 + * @return 目标键中的元素数量 + */ + suspend fun zunionstoreMax(destKey: String, keys: Array): Long { + return zunionstore(destKey, keys, null, "MAX") + } + + /** + * 计算两个或多个有序集合的差集,并将结果存储在新的键中 + * 此方法仅适用于Redis 6.2+版本 + * + * @param destKey 目标键,存储计算结果 + * @param keys 要计算差集的有序集合键数组,第一个集合是基准 + * @return 目标键中的元素数量 + */ + suspend fun zdiffstore(destKey: String, keys: Array): Long { + if (keys.isEmpty() || keys.size < 2) return 0L + + val request = Request.cmd(Command.ZDIFFSTORE) + .arg(destKey) + .arg(keys.size.toString()) + + // 添加源集合键 + keys.forEach { request.arg(it) } + + val response = redis.send(request).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 获取有序集合的大小(成员数量) + * + * @param key Redis键 + * @return 有序集合的大小 + */ + suspend fun zcard(key: String): Long { + val request = Request.cmd(Command.ZCARD) + .arg(key) + + val response = redis.send(request).coAwait() + return response?.toLong() ?: 0L + } + + /** + * 获取有序集合中所有成员 + * + * @param key Redis键 + * @return 有序集合的所有成员 + */ + suspend fun zall(key: String): List { + return zrevrange(key, 0, -1) + } + + /** + * 计算两个有序集合的差集,并返回结果 + * 此方法仅适用于Redis 6.2+版本 + * + * @param keys 要计算差集的有序集合键数组,第一个集合是基准 + * @return 差集结果 + */ + suspend fun zdiff(vararg keys: String): List { + if (keys.isEmpty() || keys.size < 2) return emptyList() + + val request = Request.cmd(Command.ZDIFF) + + // 添加集合数量 + request.arg(keys.size.toString()) + + // 添加源集合键 + keys.forEach { request.arg(it) } + + val response = redis.send(request).coAwait() + return response?.map { it.toString() } ?: emptyList() + } + + /** + * 执行 Lua 脚本 + * + * @param script Lua 脚本 + * @param keys 脚本中使用的 KEYS 参数 + * @param args 脚本中使用的 ARGV 参数 + * @return 脚本执行结果 + */ + suspend fun eval(script: String, keys: List = emptyList(), vararg args: String): List? { + val request = Request.cmd(Command.EVAL) + .arg(script) + .arg(keys.size.toString()) + + // 添加 KEYS 参数 + keys.forEach { request.arg(it) } + + // 添加 ARGV 参数 + args.forEach { request.arg(it) } + + val response = redis.send(request).coAwait() + return response?.map { it.toString() } + } + + /** + * 执行 Lua 脚本并返回整数结果 + * + * @param script Lua 脚本 + * @param keys 脚本中使用的 KEYS 参数 + * @param args 脚本中使用的 ARGV 参数 + * @return 脚本执行结果(整数) + */ + suspend fun evalToInt(script: String, keys: List = emptyList(), vararg args: String): Int? { + val result = eval(script, keys, *args) + return result?.firstOrNull()?.toIntOrNull() + } + + /** + * 执行 Lua 脚本并返回长整数结果 + * + * @param script Lua 脚本 + * @param keys 脚本中使用的 KEYS 参数 + * @param args 脚本中使用的 ARGV 参数 + * @return 脚本执行结果(长整数) + */ + suspend fun evalToLong(script: String, keys: List = emptyList(), vararg args: String): Long? { + val result = eval(script, keys, *args) + return result?.firstOrNull()?.toLongOrNull() + } + + /** + * 执行 Lua 脚本并返回布尔结果 + * + * @param script Lua 脚本 + * @param keys 脚本中使用的 KEYS 参数 + * @param args 脚本中使用的 ARGV 参数 + * @return 脚本执行结果(布尔值) + */ + suspend fun evalToBoolean(script: String, keys: List = emptyList(), vararg args: String): Boolean { + val result = eval(script, keys, *args) + return result?.firstOrNull()?.toIntOrNull() == 1 + } +} + diff --git a/vertx-demo/src/main/kotlin/app/port/aipfox/ApifoxClient.kt b/vertx-demo/src/main/kotlin/app/utils/openapi/ApifoxUtil.kt similarity index 94% rename from vertx-demo/src/main/kotlin/app/port/aipfox/ApifoxClient.kt rename to vertx-demo/src/main/kotlin/app/utils/openapi/ApifoxUtil.kt index ff0ad6d..51497dc 100644 --- a/vertx-demo/src/main/kotlin/app/port/aipfox/ApifoxClient.kt +++ b/vertx-demo/src/main/kotlin/app/utils/openapi/ApifoxUtil.kt @@ -1,16 +1,15 @@ -package app.port.aipfox +package app.utils.openapi -import app.util.openapi.OpenApiSpecGenerator import com.google.inject.Inject +import io.github.oshai.kotlinlogging.KotlinLogging import io.vertx.core.Vertx import io.vertx.core.http.HttpMethod import io.vertx.core.json.JsonObject import io.vertx.ext.web.client.WebClient import io.vertx.ext.web.client.WebClientOptions -import io.github.oshai.kotlinlogging.KotlinLogging import org.aikrai.vertx.config.Config -class ApifoxClient @Inject constructor( +class ApifoxUtil @Inject constructor( private val vertx: Vertx, ) { private val logger = KotlinLogging.logger { } @@ -47,4 +46,4 @@ class ApifoxClient @Inject constructor( } } } -} +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/util/openapi/OpenApiSpecGenerator.kt b/vertx-demo/src/main/kotlin/app/utils/openapi/OpenApiSpecGenerator.kt similarity index 99% rename from vertx-demo/src/main/kotlin/app/util/openapi/OpenApiSpecGenerator.kt rename to vertx-demo/src/main/kotlin/app/utils/openapi/OpenApiSpecGenerator.kt index 1f76c16..538396a 100644 --- a/vertx-demo/src/main/kotlin/app/util/openapi/OpenApiSpecGenerator.kt +++ b/vertx-demo/src/main/kotlin/app/utils/openapi/OpenApiSpecGenerator.kt @@ -1,4 +1,4 @@ -package app.util.openapi +package app.utils.openapi import cn.hutool.core.util.StrUtil import io.swagger.v3.core.util.Json diff --git a/vertx-demo/src/main/kotlin/app/verticle/WebVerticle.kt b/vertx-demo/src/main/kotlin/app/verticle/WebVerticle.kt index 5112ed1..47e44e7 100644 --- a/vertx-demo/src/main/kotlin/app/verticle/WebVerticle.kt +++ b/vertx-demo/src/main/kotlin/app/verticle/WebVerticle.kt @@ -2,7 +2,7 @@ package app.verticle import app.config.handler.JwtAuthHandler import app.config.handler.ResponseHandler -import app.port.aipfox.ApifoxClient +import app.utils.openapi.ApifoxUtil import com.google.inject.Inject import com.google.inject.Injector import io.vertx.core.http.HttpMethod @@ -27,7 +27,7 @@ class WebVerticle @Inject constructor( private val requestLogHandler: RequestLogHandler, private val responseHandler: ResponseHandler, private val globalErrorHandler: GlobalErrorHandler, - private val apiFoxClient: ApifoxClient, + private val apiFoxUtil: ApifoxUtil, ) : CoroutineVerticle() { private val logger = KotlinLogging.logger { } @@ -42,7 +42,7 @@ class WebVerticle @Inject constructor( .listen(serverConfig.port) .coAwait() // 生成ApiFox接口 - apiFoxClient.importOpenapi() + apiFoxUtil.importOpenapi() logger.info { "HTTP服务启动 - http://127.0.0.1:${server.actualPort()}${serverConfig.context}" } } diff --git a/vertx-demo/src/main/resources/dbmigration/1.0__initial.sql b/vertx-demo/src/main/resources/dbmigration/1.0__initial.sql index 668e732..9e422b4 100644 --- a/vertx-demo/src/main/resources/dbmigration/1.0__initial.sql +++ b/vertx-demo/src/main/resources/dbmigration/1.0__initial.sql @@ -16,12 +16,15 @@ CREATE TABLE sys_menu ( CREATE TABLE sys_user ( user_id BIGINT DEFAULT 0 NOT NULL, - user_name VARCHAR(255) DEFAULT '', - user_type VARCHAR(255) DEFAULT '', - email VARCHAR(255) DEFAULT '', - phone VARCHAR(255) DEFAULT '', - avatar VARCHAR(255) DEFAULT '', - password VARCHAR(255) DEFAULT '', + dept_id BIGINT DEFAULT 0, + user_name VARCHAR(30) DEFAULT '', + nick_name VARCHAR(30) DEFAULT '', + user_type VARCHAR(2) DEFAULT '', + email VARCHAR(50) DEFAULT '', + phonenumber VARCHAR(11) DEFAULT '', + sex INTEGER DEFAULT 2, + avatar VARCHAR(100) DEFAULT '', + password VARCHAR(100) DEFAULT '', status INTEGER DEFAULT 0, del_flag CHAR(1) DEFAULT 0, login_ip VARCHAR(255) DEFAULT '', @@ -31,9 +34,17 @@ CREATE TABLE sys_user ( -- 添加字段注释 COMMENT ON COLUMN sys_user.user_id IS '用户ID'; - - -CREATE UNIQUE INDEX idx_phone ON sys_user (phone); +COMMENT ON COLUMN sys_user.dept_id IS '部门ID'; +COMMENT ON COLUMN sys_user.user_name IS '用户账号'; +COMMENT ON COLUMN sys_user.nick_name IS '用户昵称'; +COMMENT ON COLUMN sys_user.user_type IS '用户类型'; +COMMENT ON COLUMN sys_user.email IS '用户邮箱'; +COMMENT ON COLUMN sys_user.phonenumber IS '手机号码'; +COMMENT ON COLUMN sys_user.sex IS '用户性别(0男 1女 2未知)'; +COMMENT ON COLUMN sys_user.avatar IS '头像地址'; +COMMENT ON COLUMN sys_user.password IS '密码'; +COMMENT ON COLUMN sys_user.status IS '帐号状态(0正常 1停用)'; +COMMENT ON COLUMN sys_user.del_flag IS '删除标志(0代表存在 2代表删除)'; CREATE TABLE sys_role ( diff --git a/vertx-demo/src/main/resources/dbmigration/model/1.0__initial.model.xml b/vertx-demo/src/main/resources/dbmigration/model/1.0__initial.model.xml index 1cdeb89..86a48d5 100644 --- a/vertx-demo/src/main/resources/dbmigration/model/1.0__initial.model.xml +++ b/vertx-demo/src/main/resources/dbmigration/model/1.0__initial.model.xml @@ -1,5 +1,5 @@ - + @@ -16,12 +16,15 @@ - - - - - - + + + + + + + + + diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/config/AppConfigs.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/config/AppConfigs.kt index 9c4054e..57dfeda 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/config/AppConfigs.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/config/AppConfigs.kt @@ -19,7 +19,7 @@ data class RedisConfig( val host: String, val port: Int, val db: Int, - val pass: String?, + val password: String?, val poolSize: Int = 8, val maxPoolWaiting: Int = 32 ) diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/config/FrameworkConfigModule.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/config/FrameworkConfigModule.kt index fde686c..86d3370 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/config/FrameworkConfigModule.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/config/FrameworkConfigModule.kt @@ -31,7 +31,7 @@ class FrameworkConfigModule : AbstractModule() { host = Config.getString("redis.host", "localhost"), port = Config.getInt("redis.port", 6379), db = Config.getInt("redis.database", 0), - pass = Config.getStringOrNull("redis.password"), + password = Config.getStringOrNull("redis.password"), poolSize = Config.getInt("redis.maxPoolSize", 8), maxPoolWaiting = Config.getInt("redis.maxPoolWaiting", 32) )