vertx-pj:0.0.1
This commit is contained in:
parent
f28e1341d0
commit
9f2105caf8
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,8 +3,8 @@ build/
|
|||||||
!gradle/wrapper/gradle-wrapper.jar
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
!**/src/main/**/build/
|
!**/src/main/**/build/
|
||||||
!**/src/test/**/build/
|
!**/src/test/**/build/
|
||||||
config/
|
/config
|
||||||
gradle/
|
/gradle
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea
|
.idea
|
||||||
|
|||||||
7
vertx-demo/src/main/kotlin/app/config/Constant.kt
Normal file
7
vertx-demo/src/main/kotlin/app/config/Constant.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package app.config
|
||||||
|
|
||||||
|
class Constant {
|
||||||
|
companion object {
|
||||||
|
const val USER = "user:"
|
||||||
|
}
|
||||||
|
}
|
||||||
93
vertx-demo/src/main/kotlin/app/config/InjectConfig.kt
Normal file
93
vertx-demo/src/main/kotlin/app/config/InjectConfig.kt
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package app.config
|
||||||
|
|
||||||
|
import app.config.auth.JWTAuthProvider
|
||||||
|
import cn.hutool.core.lang.Snowflake
|
||||||
|
import cn.hutool.core.util.IdUtil
|
||||||
|
import com.google.inject.*
|
||||||
|
import com.google.inject.name.Names
|
||||||
|
import io.vertx.core.Vertx
|
||||||
|
import io.vertx.core.http.HttpServer
|
||||||
|
import io.vertx.core.http.HttpServerOptions
|
||||||
|
import io.vertx.ext.auth.jwt.JWTAuth
|
||||||
|
import io.vertx.mysqlclient.MySQLBuilder
|
||||||
|
import io.vertx.mysqlclient.MySQLConnectOptions
|
||||||
|
import io.vertx.pgclient.PgBuilder
|
||||||
|
import io.vertx.pgclient.PgConnectOptions
|
||||||
|
import io.vertx.sqlclient.Pool
|
||||||
|
import io.vertx.sqlclient.PoolOptions
|
||||||
|
import io.vertx.sqlclient.SqlClient
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import org.aikrai.vertx.config.Config
|
||||||
|
import org.aikrai.vertx.config.DefaultScope
|
||||||
|
import org.aikrai.vertx.db.tx.TxMgrHolder.initTxMgr
|
||||||
|
|
||||||
|
object InjectConfig {
|
||||||
|
suspend fun configure(vertx: Vertx): Injector {
|
||||||
|
Config.init(vertx)
|
||||||
|
return Guice.createInjector(InjectorModule(vertx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InjectorModule(
|
||||||
|
private val vertx: Vertx,
|
||||||
|
) : AbstractModule() {
|
||||||
|
override fun configure() {
|
||||||
|
val pool = getDbPool().also { initTxMgr(it) }
|
||||||
|
val coroutineScope = DefaultScope(vertx)
|
||||||
|
|
||||||
|
for ((key, value) in Config.getConfigMap()) {
|
||||||
|
bind(String::class.java).annotatedWith(Names.named(key)).toInstance(value.toString())
|
||||||
|
}
|
||||||
|
bind(Vertx::class.java).toInstance(vertx)
|
||||||
|
bind(CoroutineScope::class.java).toInstance(coroutineScope)
|
||||||
|
bind(HttpServer::class.java).toInstance(vertx.createHttpServer(HttpServerOptions()))
|
||||||
|
bind(Snowflake::class.java).toInstance(IdUtil.getSnowflake())
|
||||||
|
bind(JWTAuth::class.java).toProvider(JWTAuthProvider::class.java).`in`(Singleton::class.java)
|
||||||
|
|
||||||
|
// 绑定 DbPool 为单例
|
||||||
|
bind(Pool::class.java).toInstance(pool)
|
||||||
|
bind(SqlClient::class.java).toInstance(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDbPool(): Pool {
|
||||||
|
// val type = configMap["databases.type"].toString()
|
||||||
|
// val name = configMap["databases.name"].toString()
|
||||||
|
// val host = configMap["databases.host"].toString()
|
||||||
|
// val port = configMap["databases.port"].toString()
|
||||||
|
// val user = configMap["databases.username"].toString()
|
||||||
|
// val password = configMap["databases.password"].toString()
|
||||||
|
// val dbMap = Config.getKey("databases") as Map<String, String>
|
||||||
|
val type = Config.getKey("databases.type").toString()
|
||||||
|
val name = Config.getKey("databases.name").toString()
|
||||||
|
val host = Config.getKey("databases.host").toString()
|
||||||
|
val port = Config.getKey("databases.port").toString()
|
||||||
|
val user = Config.getKey("databases.username").toString()
|
||||||
|
val password = Config.getKey("databases.password").toString()
|
||||||
|
|
||||||
|
val poolOptions = PoolOptions().setMaxSize(10)
|
||||||
|
val pool = when (type.lowercase()) {
|
||||||
|
"mysql" -> {
|
||||||
|
val clientOptions = MySQLConnectOptions()
|
||||||
|
.setHost(host)
|
||||||
|
.setPort(port.toInt())
|
||||||
|
.setDatabase(name)
|
||||||
|
.setUser(user)
|
||||||
|
.setPassword(password)
|
||||||
|
.setTcpKeepAlive(true)
|
||||||
|
MySQLBuilder.pool().connectingTo(clientOptions).with(poolOptions).using(vertx).build()
|
||||||
|
}
|
||||||
|
"postgre", "postgresql" -> {
|
||||||
|
val clientOptions = PgConnectOptions()
|
||||||
|
.setHost(host)
|
||||||
|
.setPort(port.toInt())
|
||||||
|
.setDatabase(name)
|
||||||
|
.setUser(user)
|
||||||
|
.setPassword(password)
|
||||||
|
.setTcpKeepAlive(true)
|
||||||
|
PgBuilder.pool().connectingTo(clientOptions).with(poolOptions).using(vertx).build()
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException("Unsupported database type: $type")
|
||||||
|
}
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
}
|
||||||
31
vertx-demo/src/main/kotlin/app/config/auth/AuthHandler.kt
Normal file
31
vertx-demo/src/main/kotlin/app/config/auth/AuthHandler.kt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package app.config.auth
|
||||||
|
|
||||||
|
import app.config.Constant
|
||||||
|
import app.domain.user.UserRepository
|
||||||
|
import app.util.CacheUtil
|
||||||
|
import com.google.inject.Inject
|
||||||
|
import io.vertx.ext.auth.jwt.JWTAuth
|
||||||
|
import org.aikrai.vertx.auth.Attributes
|
||||||
|
import org.aikrai.vertx.auth.AuthUser
|
||||||
|
import org.aikrai.vertx.auth.Principal
|
||||||
|
import org.aikrai.vertx.auth.TokenUtil
|
||||||
|
|
||||||
|
class AuthHandler @Inject constructor(
|
||||||
|
private val jwtAuth: JWTAuth,
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
private val cacheUtil: CacheUtil
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun handle(token: String): AuthUser? {
|
||||||
|
val userInfo = TokenUtil.authenticate(jwtAuth, token) ?: return null
|
||||||
|
val userId = userInfo.principal().getString("id").toLong()
|
||||||
|
val user = cacheUtil.get(Constant.USER + userId) ?: userRepository.get(userId)?.let {
|
||||||
|
cacheUtil.put(Constant.USER + userId, it)
|
||||||
|
} ?: return null
|
||||||
|
return AuthUser(
|
||||||
|
Principal(userId, user),
|
||||||
|
// get roles and permissions from database
|
||||||
|
Attributes(setOf("admin"), setOf("user:list")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package app.config.auth
|
||||||
|
|
||||||
|
import com.google.inject.Inject
|
||||||
|
import com.google.inject.Provider
|
||||||
|
import com.google.inject.name.Named
|
||||||
|
import io.vertx.core.Vertx
|
||||||
|
import io.vertx.ext.auth.PubSecKeyOptions
|
||||||
|
import io.vertx.ext.auth.jwt.JWTAuth
|
||||||
|
import io.vertx.ext.auth.jwt.JWTAuthOptions
|
||||||
|
|
||||||
|
class JWTAuthProvider @Inject constructor(
|
||||||
|
private val vertx: Vertx,
|
||||||
|
@Named("jwt.key") private val key: String
|
||||||
|
) : Provider<JWTAuth> {
|
||||||
|
override fun get(): JWTAuth {
|
||||||
|
val options = JWTAuthOptions()
|
||||||
|
.addPubSecKey(
|
||||||
|
PubSecKeyOptions()
|
||||||
|
.setAlgorithm("HS256")
|
||||||
|
.setBuffer(key)
|
||||||
|
)
|
||||||
|
return JWTAuth.create(vertx, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
package app.config.auth
|
||||||
|
|
||||||
|
import io.vertx.ext.web.RoutingContext
|
||||||
|
import io.vertx.ext.web.handler.AuthenticationHandler
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.aikrai.vertx.utlis.Meta
|
||||||
|
|
||||||
|
class JwtAuthenticationHandler(
|
||||||
|
private val coroutineScope: CoroutineScope,
|
||||||
|
private val authHandler: AuthHandler,
|
||||||
|
private val context: String,
|
||||||
|
) : AuthenticationHandler {
|
||||||
|
|
||||||
|
var exclude = mutableListOf(
|
||||||
|
"/auth/**",
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun handle(event: RoutingContext) {
|
||||||
|
val path = event.request().path().replace("$context/", "/").replace("//", "/")
|
||||||
|
if (isPathExcluded(path, exclude)) {
|
||||||
|
event.next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val authorization = event.request().getHeader("Authorization") ?: null
|
||||||
|
if (authorization == null || !authorization.startsWith("token ")) {
|
||||||
|
event.fail(401, Meta.unauthorized("无效Token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val token = authorization.substring(6)
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
val authUser = authHandler.handle(token)
|
||||||
|
if (authUser != null) {
|
||||||
|
event.setUser(authUser)
|
||||||
|
event.next()
|
||||||
|
} else {
|
||||||
|
event.fail(401, Meta.unauthorized("token"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPathExcluded(path: String, excludePatterns: List<String>): Boolean {
|
||||||
|
for (pattern in excludePatterns) {
|
||||||
|
val regexPattern = pattern
|
||||||
|
.replace("**", ".+")
|
||||||
|
.replace("*", "[^/]+")
|
||||||
|
.replace("?", ".")
|
||||||
|
val isExclude = path.matches(regexPattern.toRegex())
|
||||||
|
if (isExclude) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
databases:
|
||||||
|
has-open: true
|
||||||
|
type: mysql
|
||||||
|
driver-class-name: com.mysql.jdbc.Driver
|
||||||
|
name: vertx-demo
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 3306
|
||||||
|
username: root
|
||||||
|
password: 123456
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
databases:
|
||||||
|
has-open: true
|
||||||
|
type: mysql
|
||||||
|
driver-class-name: com.mysql.jdbc.Driver
|
||||||
|
name: vertx-demo
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 3306
|
||||||
|
username: root
|
||||||
|
password: 123456
|
||||||
92
vertx-fw/src/main/kotlin/org/aikrai/vertx/config/Config.kt
Normal file
92
vertx-fw/src/main/kotlin/org/aikrai/vertx/config/Config.kt
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package org.aikrai.vertx.config
|
||||||
|
|
||||||
|
import io.vertx.config.ConfigRetriever
|
||||||
|
import io.vertx.config.ConfigRetrieverOptions
|
||||||
|
import io.vertx.config.ConfigStoreOptions
|
||||||
|
import io.vertx.core.Vertx
|
||||||
|
import io.vertx.core.json.JsonArray
|
||||||
|
import io.vertx.core.json.JsonObject
|
||||||
|
import io.vertx.kotlin.coroutines.coAwait
|
||||||
|
import org.aikrai.vertx.utlis.FlattenUtil
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
object Config {
|
||||||
|
private val retriever = AtomicReference<ConfigRetriever?>(null)
|
||||||
|
private var configMap = emptyMap<String, Any>()
|
||||||
|
|
||||||
|
suspend fun init(vertx: Vertx) {
|
||||||
|
if (retriever.get() != null) return
|
||||||
|
val configRetriever = load(vertx)
|
||||||
|
val cas = retriever.compareAndSet(null, configRetriever)
|
||||||
|
if (cas) {
|
||||||
|
val configObj = configRetriever.config.coAwait()
|
||||||
|
configMap = FlattenUtil.flattenJsonObject(configObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getKey(key: String): Any? {
|
||||||
|
if (retriever.get() == null) throw IllegalStateException("Config not initialized")
|
||||||
|
// 检查 configMap 中是否存在指定的 key
|
||||||
|
return if (configMap.containsKey(key)) {
|
||||||
|
configMap[key]
|
||||||
|
} else {
|
||||||
|
// 找到所有以 key 开头的条目
|
||||||
|
configMap.filterKeys { it.startsWith(key) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getConfigMap(): Map<String, Any> {
|
||||||
|
return configMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun load(vertx: Vertx): ConfigRetriever {
|
||||||
|
val sysStore = ConfigStoreOptions().setType("sys")
|
||||||
|
val envStore = ConfigStoreOptions().setType("env").setConfig(JsonObject().put("raw-data", true))
|
||||||
|
val bootstrapStore = ConfigStoreOptions().setType("file").setFormat("yaml")
|
||||||
|
.setConfig(JsonObject().put("path", "bootstrap.yml"))
|
||||||
|
|
||||||
|
val bootstrapOptions = ConfigRetrieverOptions()
|
||||||
|
.addStore(bootstrapStore)
|
||||||
|
.addStore(sysStore)
|
||||||
|
.addStore(envStore)
|
||||||
|
|
||||||
|
val bootstrapRetriever = ConfigRetriever.create(vertx, bootstrapOptions)
|
||||||
|
val bootstrapConfig = bootstrapRetriever.config.coAwait()
|
||||||
|
val useDir = bootstrapConfig.getString("user.dir")
|
||||||
|
val environment = bootstrapConfig.getJsonObject("server").getString("active")
|
||||||
|
|
||||||
|
// 创建资源目录配置存储
|
||||||
|
val rDirectoryStore = createDirectoryStore("$useDir/src/main/resources/config")
|
||||||
|
// 创建项目根目录配置存储
|
||||||
|
val pDirectoryStore = createDirectoryStore(useDir)
|
||||||
|
// 创建环境相关配置存储
|
||||||
|
val directoryStore = createDirectoryStore("config${if (!environment.isNullOrBlank()) "/$environment" else ""}")
|
||||||
|
|
||||||
|
// 后加载的配置会覆盖前面加载的相同的配置
|
||||||
|
val options = ConfigRetrieverOptions()
|
||||||
|
// 项目的resources目录下
|
||||||
|
.addStore(rDirectoryStore)
|
||||||
|
// 项目根目录下
|
||||||
|
.addStore(pDirectoryStore)
|
||||||
|
// 项目根目录下的config目录
|
||||||
|
.addStore(directoryStore)
|
||||||
|
// bootstrap.yml 文件
|
||||||
|
.addStore(bootstrapStore)
|
||||||
|
return ConfigRetriever.create(vertx, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDirectoryStore(path: String): ConfigStoreOptions {
|
||||||
|
return ConfigStoreOptions()
|
||||||
|
.setType("directory")
|
||||||
|
.setConfig(
|
||||||
|
JsonObject().put("path", path).put(
|
||||||
|
"filesets",
|
||||||
|
JsonArray()
|
||||||
|
.add(JsonObject().put("pattern", "*.yml").put("format", "yaml"))
|
||||||
|
.add(JsonObject().put("pattern", "*.yaml").put("format", "yaml"))
|
||||||
|
.add(JsonObject().put("pattern", "*.properties").put("format", "properties"))
|
||||||
|
.add(JsonObject().put("pattern", "*.json").put("format", "json"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package org.aikrai.vertx.config
|
||||||
|
|
||||||
|
import io.vertx.core.Vertx
|
||||||
|
import io.vertx.kotlin.coroutines.dispatcher
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class DefaultScope(private val vertx: Vertx) : CoroutineScope {
|
||||||
|
override var coroutineContext: CoroutineContext by Delegates.notNull()
|
||||||
|
|
||||||
|
init {
|
||||||
|
coroutineContext = vertx.orCreateContext.dispatcher()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package org.aikrai.vertx.config
|
||||||
|
|
||||||
|
import io.vertx.mysqlclient.MySQLException
|
||||||
|
import io.vertx.pgclient.PgException
|
||||||
|
import org.aikrai.vertx.jackson.JsonUtil
|
||||||
|
import org.aikrai.vertx.utlis.Meta
|
||||||
|
import java.sql.SQLException
|
||||||
|
|
||||||
|
object FailureParser {
|
||||||
|
private fun Throwable.toMeta(): Meta {
|
||||||
|
val error = this as? Meta
|
||||||
|
if (error != null) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = javaClass.simpleName
|
||||||
|
|
||||||
|
val pgException = this as? PgException
|
||||||
|
if (pgException != null) {
|
||||||
|
return Meta(name, pgException.errorMessage ?: "", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mysqlException = this as? MySQLException
|
||||||
|
if (mysqlException != null) {
|
||||||
|
return Meta(name, mysqlException.message ?: "", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val message = if (message != null) message.orEmpty() else toString()
|
||||||
|
return Meta(name, message, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Throwable.info(): String {
|
||||||
|
if (this is Meta) {
|
||||||
|
return JsonUtil.toJsonStr(this)
|
||||||
|
}
|
||||||
|
return stackTraceToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Failure(val statusCode: Int, val response: Meta)
|
||||||
|
|
||||||
|
fun parse(statusCode: Int, error: Throwable): Failure {
|
||||||
|
return when (error) {
|
||||||
|
is SQLException -> Failure(statusCode, Meta.failure(error.javaClass.name, "执行错误"))
|
||||||
|
else -> Failure(statusCode, error.toMeta())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user