refactor(vertx-demo):重构项目目录结构
This commit is contained in:
parent
047717ad71
commit
95f76404c2
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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<Redis> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<Long, Role>
|
||||
@ -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
|
||||
@ -1,4 +1,4 @@
|
||||
package app.data.domain.account.modle
|
||||
package app.data.dto.account
|
||||
|
||||
import app.data.domain.role.Role
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package app.data.domain.account
|
||||
package app.data.dto.account
|
||||
|
||||
data class LoginDTO(
|
||||
var username: String,
|
||||
@ -1,4 +1,4 @@
|
||||
package app.base.domain.auth.modle
|
||||
package app.data.dto.account
|
||||
|
||||
class LoginUser {
|
||||
var accountId: Long = 0L
|
||||
@ -1,4 +1,4 @@
|
||||
package app.data.domain.menu.modle
|
||||
package app.data.dto.menu
|
||||
|
||||
import app.data.domain.menu.Menu
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
28
vertx-demo/src/main/kotlin/app/data/emun/SexType.kt
Normal file
28
vertx-demo/src/main/kotlin/app/data/emun/SexType.kt
Normal file
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
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
|
||||
|
||||
@ -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<Long, Role>
|
||||
@ -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<Account> {
|
||||
return queryBuilder()
|
||||
.eq(!userName.isNullOrBlank(), Account::userName, userName)
|
||||
.eq(!phone.isNullOrBlank(), Account::phone, phone)
|
||||
.eq(!phone.isNullOrBlank(), Account::phonenumber, phone)
|
||||
.getList()
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -1,5 +1,7 @@
|
||||
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
|
||||
@ -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 {
|
||||
@ -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
|
||||
|
||||
@ -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<String>(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))
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package app.util
|
||||
package app.utils
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.google.inject.Singleton
|
||||
835
vertx-demo/src/main/kotlin/app/utils/RedisUtil.kt
Normal file
835
vertx-demo/src/main/kotlin/app/utils/RedisUtil.kt
Normal file
@ -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 <T> 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 <T> 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 <T> 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<String>): 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 <T> setList(key: String, dataList: List<T>): Long {
|
||||
val args = mutableListOf<String>().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 <T> getList(key: String): List<T>? {
|
||||
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 <T> setSet(key: String, dataSet: Set<T>): Long {
|
||||
val args = mutableListOf<String>().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 <T> getSet(key: String): Set<T>? {
|
||||
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 <T> 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 <T> 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 <T> sRemove(key: String, vararg values: T): Long {
|
||||
val args = mutableListOf<String>().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<String> {
|
||||
val response = redis.send(Request.cmd(Command.SMEMBERS, key)).coAwait()
|
||||
return response?.map { it.toString() } ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存Map
|
||||
*
|
||||
* @param key
|
||||
* @param dataMap
|
||||
*/
|
||||
suspend fun <T> setMap(key: String, dataMap: Map<String, T>): Boolean {
|
||||
if (dataMap.isEmpty()) return false
|
||||
val args = mutableListOf<String>().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 <T> getMap(key: String): Map<String, T>? {
|
||||
val response = redis.send(Request.cmd(Command.HGETALL, key)).coAwait()
|
||||
if (response == null || response.size() == 0) return null
|
||||
|
||||
val map = mutableMapOf<String, T>()
|
||||
// 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 <T> 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 <T> 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 <T> getMultiMapValue(key: String, hKeys: Collection<String>): List<T?>? {
|
||||
val args = mutableListOf<String>().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<String>? {
|
||||
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 <T> 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 <T> 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<String?> {
|
||||
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<String> {
|
||||
// 使用可变参数列表而不是数组,避免类型转换问题
|
||||
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<String> {
|
||||
val keys = mutableListOf<String>()
|
||||
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<String>,
|
||||
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<String>): 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<String>): Long {
|
||||
return zunionstore(destKey, keys, null, "MAX")
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个或多个有序集合的差集,并将结果存储在新的键中
|
||||
* 此方法仅适用于Redis 6.2+版本
|
||||
*
|
||||
* @param destKey 目标键,存储计算结果
|
||||
* @param keys 要计算差集的有序集合键数组,第一个集合是基准
|
||||
* @return 目标键中的元素数量
|
||||
*/
|
||||
suspend fun zdiffstore(destKey: String, keys: Array<String>): 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<String> {
|
||||
return zrevrange(key, 0, -1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个有序集合的差集,并返回结果
|
||||
* 此方法仅适用于Redis 6.2+版本
|
||||
*
|
||||
* @param keys 要计算差集的有序集合键数组,第一个集合是基准
|
||||
* @return 差集结果
|
||||
*/
|
||||
suspend fun zdiff(vararg keys: String): List<String> {
|
||||
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<String> = emptyList(), vararg args: String): List<String>? {
|
||||
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<String> = 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<String> = 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<String> = emptyList(), vararg args: String): Boolean {
|
||||
val result = eval(script, keys, *args)
|
||||
return result?.firstOrNull()?.toIntOrNull() == 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 { }
|
||||
@ -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
|
||||
@ -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}" }
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<migration generated="2025-03-21T15:27:45.6470429">
|
||||
<migration generated="2025-05-12T11:18:05.3077594">
|
||||
<changeSet type="apply">
|
||||
<createTable name="sys_menu" pkName="pk_sys_menu">
|
||||
<column name="menu_id" notnull="true" primaryKey="true" type="BIGINT"/>
|
||||
@ -16,12 +16,15 @@
|
||||
</createTable>
|
||||
<createTable name="sys_user" pkName="pk_sys_user">
|
||||
<column name="user_id" notnull="true" primaryKey="true" type="BIGINT"/>
|
||||
<column name="user_name" type="VARCHAR(255)"/>
|
||||
<column name="user_type" type="VARCHAR(255)"/>
|
||||
<column name="email" type="VARCHAR(255)"/>
|
||||
<column name="phone" type="VARCHAR(255)"/>
|
||||
<column name="avatar" type="VARCHAR(255)"/>
|
||||
<column name="password" type="VARCHAR(255)"/>
|
||||
<column name="dept_id" type="BIGINT"/>
|
||||
<column name="user_name" type="VARCHAR(30)"/>
|
||||
<column name="nick_name" type="VARCHAR(30)"/>
|
||||
<column name="user_type" type="VARCHAR(2)"/>
|
||||
<column name="email" type="VARCHAR(50)"/>
|
||||
<column name="phonenumber" type="VARCHAR(11)"/>
|
||||
<column defaultValue="2" name="sex" type="INTEGER"/>
|
||||
<column name="avatar" type="VARCHAR(100)"/>
|
||||
<column name="password" type="VARCHAR(100)"/>
|
||||
<column defaultValue="0" name="status" type="INTEGER"/>
|
||||
<column name="del_flag" type="CHAR(1)"/>
|
||||
<column name="login_ip" type="VARCHAR(255)"/>
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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)
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user