fix: DB connect

This commit is contained in:
SOR
2026-08-18 18:41:39 +08:00
parent 1938fb120b
commit e71f84a943
11 changed files with 125 additions and 41 deletions
@@ -67,14 +67,14 @@ class SecurityConfig(
writeJsonResponse( writeJsonResponse(
exchange, exchange,
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
ApiResponse.unauthenticated<Any>(ex.message) ApiResponse.unauthenticated(ex.message)
) )
} }
.accessDeniedHandler { exchange, ex -> .accessDeniedHandler { exchange, ex ->
writeJsonResponse( writeJsonResponse(
exchange, exchange,
HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN,
ApiResponse.forbidden<Any>(ex.message) ApiResponse.forbidden(ex.message)
) )
} }
} }
@@ -112,7 +112,7 @@ class SecurityConfig(
writeJsonResponse( writeJsonResponse(
webFilterExchange.exchange, webFilterExchange.exchange,
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
ApiResponse.unauthenticated<Any>(exception.message) ApiResponse.unauthenticated(exception.message)
) )
} }
return filter return filter
@@ -1,6 +1,9 @@
package com.github.sor2171.backend.config package com.github.sor2171.backend.config
import com.github.sor2171.backend.utils.BigDecimalSerializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
@@ -9,6 +12,9 @@ class SerializationConfig {
@Bean @Bean
fun kotlinxSerializationJson(): Json = Json { fun kotlinxSerializationJson(): Json = Json {
serializersModule = SerializersModule {
contextual(BigDecimalSerializer)
}
ignoreUnknownKeys = true ignoreUnknownKeys = true
isLenient = true isLenient = true
encodeDefaults = false encodeDefaults = false
@@ -17,12 +17,12 @@ class TestController(
fun test(): Mono<String> = Mono.just("Hello Kotlin WebFlux!") fun test(): Mono<String> = Mono.just("Hello Kotlin WebFlux!")
@GetMapping("/wstoken") @GetMapping("/wstoken")
fun testToken(): Mono<ApiResponse<String>> = fun testToken(): Mono<ApiResponse<Any?>> =
wsTokenService.registerToken(ExampleHandler::class) wsTokenService.registerToken(ExampleHandler::class)
.map { token -> .map { token ->
ApiResponse.success(data = token) ApiResponse.success<Any?>(data = token)
} }
.onErrorResume { e -> .onErrorResume { e ->
Mono.just(ApiResponse.innerError(e.message)) Mono.just(ApiResponse.failure(500, null, e.message))
} }
} }
@@ -1,10 +1,11 @@
package com.github.sor2171.backend.controller.v1 package com.github.sor2171.backend.controller.v1
import com.github.sor2171.backend.entity.ApiResponse import com.github.sor2171.backend.entity.ApiResponse
import com.github.sor2171.backend.entity.enums.EmailType import com.github.sor2171.backend.entity.enums.EmailType
import com.github.sor2171.backend.entity.vo.request.EmailRegisterVO import com.github.sor2171.backend.entity.vo.request.EmailRegisterVO
import com.github.sor2171.backend.entity.vo.request.PasswordResetVO import com.github.sor2171.backend.entity.vo.request.PasswordResetVO
import com.github.sor2171.backend.service.v1.AuthorizationService import com.github.sor2171.backend.service.v1.AuthorizationService
import com.github.sor2171.backend.utils.Const
import jakarta.validation.Valid import jakarta.validation.Valid
import jakarta.validation.constraints.Email import jakarta.validation.constraints.Email
import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotBlank
@@ -31,21 +32,21 @@ class AuthorizeController(
logger.info("Trying to ask code") logger.info("Trying to ask code")
val ip = exchange.request.remoteAddress?.address?.hostAddress ?: "unknown" val ip = exchange.request.remoteAddress?.address?.hostAddress ?: "unknown"
return service.askEmailVerifyCode(type, email, ip) return service.askEmailVerifyCode(type, email, ip)
.map(::messageHandler) .map(Const::messageHandler)
} }
@PostMapping("/register") @PostMapping("/register")
fun emailRegister(@RequestBody @Valid vo: EmailRegisterVO): Mono<ApiResponse<Any?>> { fun emailRegister(@RequestBody @Valid vo: EmailRegisterVO): Mono<ApiResponse<Any?>> {
logger.info("Trying to register") logger.info("Trying to register")
return service.registerEmailAccount(vo) return service.registerEmailAccount(vo)
.map(::messageHandler) .map(Const::messageHandler)
} }
@PostMapping("/reset") @PostMapping("/reset")
fun emailResetPassword(@RequestBody @Valid vo: PasswordResetVO): Mono<ApiResponse<Any?>> { fun emailResetPassword(@RequestBody @Valid vo: PasswordResetVO): Mono<ApiResponse<Any?>> {
logger.info("Trying to reset password") logger.info("Trying to reset password")
return service.resetEmailAccountPassword(vo) return service.resetEmailAccountPassword(vo)
.map(::messageHandler) .map(Const::messageHandler)
} }
@GetMapping("/logout") @GetMapping("/logout")
@@ -68,12 +69,4 @@ class AuthorizeController(
?.map { ApiResponse.success(it) } ?.map { ApiResponse.success(it) }
?: Mono.just(ApiResponse.failure(401, null, "Please login with password.")) ?: Mono.just(ApiResponse.failure(401, null, "Please login with password."))
} }
private fun messageHandler(wrongMessage: String): ApiResponse<Any?> {
return if (wrongMessage.isBlank()) {
ApiResponse.success()
} else {
ApiResponse.failure(400, null, wrongMessage)
}
}
} }
@@ -6,7 +6,7 @@ import kotlinx.serialization.json.Json
@Serializable @Serializable
data class ApiResponse<T>( data class ApiResponse<T>(
val code: Int, val code: Int,
val data: T?, val data: T,
val message: String val message: String
) { ) {
companion object { companion object {
@@ -15,24 +15,21 @@ data class ApiResponse<T>(
} }
// 针对无 data 返回的情况,统一使用 Any? // 针对无 data 返回的情况,统一使用 Any?
fun <T> success(message: String = "Success"): ApiResponse<T> { fun success(message: String = "Success"): ApiResponse<Any?> {
return ApiResponse(200, null, message) return ApiResponse(200, null, message)
} }
fun <T> failure(code: Int, data: T?, message: String?): ApiResponse<T> { fun <T> failure(code: Int = 401, data: T, message: String?): ApiResponse<T> {
return ApiResponse(code, data, message ?: "Failure") return ApiResponse(code, data, message ?: "Failure")
} }
fun <T> unauthenticated(message: String?) = fun unauthenticated(message: String?) =
failure<T>(401, null, message) failure(401, null, message)
fun <T> forbidden(message: String?) = fun forbidden(message: String?) =
failure<T>(403, null, message) failure(403, null, message)
fun <T> innerError(message: String?) = fun logoutFailed(message: String = ""): ApiResponse<Any?> {
failure<T>(500, null, message)
fun <T> logoutFailed(message: String = ""): ApiResponse<T> {
val formatMessage = if (message.isBlank()) "" else ": $message" val formatMessage = if (message.isBlank()) "" else ": $message"
return failure(400, null, "Logout Failed$formatMessage") return failure(400, null, "Logout Failed$formatMessage")
} }
@@ -23,9 +23,9 @@ class FlowLimitFilter(
@param:Value($$"${app.flow-limit.count}") @param:Value($$"${app.flow-limit.count}")
private val limitCount: Int, private val limitCount: Int,
@param:Value($$"${app.flow-limit.limit-time}") @param:Value($$"${app.flow-limit.limit-time}")
private val limitTime: Long, private val limitTime: Int,
@param:Value($$"${app.flow-limit.ban-time}") @param:Value($$"${app.flow-limit.ban-time}")
private val expireTime: Long, private val expireTime: Int,
) : WebFilter { ) : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> { override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
@@ -45,7 +45,7 @@ class FlowLimitFilter(
response.statusCode = HttpStatus.FORBIDDEN response.statusCode = HttpStatus.FORBIDDEN
response.headers.contentType = MediaType.APPLICATION_JSON response.headers.contentType = MediaType.APPLICATION_JSON
val buffer = response.bufferFactory() val buffer = response.bufferFactory()
.wrap(ApiResponse.forbidden<Any>("Too many requests").toJsonString().toByteArray()) .wrap(ApiResponse.forbidden("Too many requests").toJsonString().toByteArray())
return response.writeWith(Mono.just(buffer)) return response.writeWith(Mono.just(buffer))
} }
@@ -61,14 +61,14 @@ class FlowLimitFilter(
if (count == 1L) { if (count == 1L) {
template.expire( template.expire(
counterKey, counterKey,
Duration.ofSeconds(limitTime) Duration.ofSeconds(limitTime.toLong())
).thenReturn(true) ).thenReturn(true)
} else if (count > limitCount) { } else if (count > limitCount) {
template.opsForValue() template.opsForValue()
.set( .set(
blockKey, blockKey,
"1", "1",
Duration.ofSeconds(expireTime) Duration.ofSeconds(expireTime.toLong())
) )
.thenReturn(false) .thenReturn(false)
} else { } else {
@@ -0,0 +1,49 @@
package com.github.sor2171.backend.mapper.typehandler
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.toJavaLocalDateTime
import kotlinx.datetime.toKotlinLocalDateTime
import org.apache.ibatis.type.BaseTypeHandler
import org.apache.ibatis.type.JdbcType
import org.apache.ibatis.type.MappedJdbcTypes
import org.apache.ibatis.type.MappedTypes
import java.sql.CallableStatement
import java.sql.PreparedStatement
import java.sql.ResultSet
@MappedTypes(LocalDateTime::class)
@MappedJdbcTypes(JdbcType.TIMESTAMP)
class TimeTypeHandler : BaseTypeHandler<LocalDateTime>() {
override fun setNonNullParameter(
ps: PreparedStatement,
i: Int,
parameter: LocalDateTime,
jdbcType: JdbcType?
) {
ps.setObject(i, parameter.toJavaLocalDateTime())
}
override fun getNullableResult(
rs: ResultSet,
columnName: String
): LocalDateTime? =
rs.getTimestamp(columnName)
?.toLocalDateTime()
?.toKotlinLocalDateTime()
override fun getNullableResult(
rs: ResultSet,
columnIndex: Int
): LocalDateTime? =
rs.getTimestamp(columnIndex)
?.toLocalDateTime()
?.toKotlinLocalDateTime()
override fun getNullableResult(
cs: CallableStatement,
columnIndex: Int
): LocalDateTime? =
cs.getTimestamp(columnIndex)
?.toLocalDateTime()
?.toKotlinLocalDateTime()
}
@@ -0,0 +1,29 @@
package com.github.sor2171.backend.utils
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonPrimitive
import java.math.BigDecimal
object BigDecimalSerializer : KSerializer<BigDecimal> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: BigDecimal) {
encoder.encodeString(value.toPlainString())
}
override fun deserialize(decoder: Decoder): BigDecimal {
val element = (decoder as JsonDecoder).decodeJsonElement()
if (element !is JsonPrimitive) {
throw SerializationException("Expected JSON primitive, got $element")
}
return element.content.toBigDecimal()
}
}
@@ -1,5 +1,6 @@
package com.github.sor2171.backend.utils package com.github.sor2171.backend.utils
import com.github.sor2171.backend.entity.ApiResponse
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
object Const { object Const {
@@ -14,5 +15,15 @@ object Const {
const val WS_REQUEST_TOKEN = "ws:token:" const val WS_REQUEST_TOKEN = "ws:token:"
val INTERNAL_ERROR_MONO = Mono.just("something went wrong. Please contact the administrator.") const val INTERNAL_ERROR_STR = "something went wrong. Please contact the administrator."
val INTERNAL_ERROR_MONO = Mono.just(INTERNAL_ERROR_STR)
// for Controllers
fun messageHandler(wrongMessage: String): ApiResponse<Any?> {
return if (wrongMessage.isBlank()) {
ApiResponse.success()
} else {
ApiResponse.failure(400, null, wrongMessage)
}
}
} }
@@ -10,8 +10,8 @@ import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.time.Duration
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
@Component @Component
class JwtUtils( class JwtUtils(
@@ -44,8 +44,7 @@ class JwtUtils(
template.opsForValue().set( template.opsForValue().set(
Const.JWT_BLACK_LIST + uuid, Const.JWT_BLACK_LIST + uuid,
"deleted", "deleted",
expire, Duration.ofMillis(expire)
TimeUnit.MILLISECONDS
) )
return true return true
} }
@@ -35,7 +35,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus: mybatis-plus:
type-handlers-package: com.github.sor2171.backend.config type-handlers-package: club.itstudio.itmanager.mapper.typehandler
app: app:
flow-limit: flow-limit: