Skip to content
Open
6 changes: 2 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ plugins {
val ktVersion = "2.2.0"
kotlin("jvm") version ktVersion
kotlin("plugin.spring") version ktVersion
kotlin("plugin.serialization") version ktVersion
kotlin("kapt") version ktVersion

id("org.jlleitschuh.gradle.ktlint") version "13.0.0"
Expand Down Expand Up @@ -68,10 +69,10 @@ dependencies {
kapt("com.github.therapi:therapi-runtime-javadoc-scribe:0.15.0")

// kotlin
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test")

// kotlin-logging
Expand All @@ -80,7 +81,6 @@ dependencies {
// ktorm connect with spring-jdbc
implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("org.ktorm:ktorm-core:$ktormVersion")
implementation("org.ktorm:ktorm-jackson:$ktormVersion")
implementation("org.ktorm:ktorm-support-postgresql:$ktormVersion")
implementation("org.postgresql:postgresql:42.7.7")
// hutool 的邮箱工具类依赖
Expand All @@ -101,8 +101,6 @@ dependencies {
implementation("com.github.ben-manes.caffeine:caffeine:3.2.2")
implementation("com.networknt:json-schema-validator:1.5.8")

implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")

swaggerCodegen("org.openapitools:openapi-generator-cli:7.14.0")

implementation("com.belerweb:pinyin4j:2.5.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package plus.maa.backend.common.controller

import kotlinx.serialization.Serializable

@Serializable
data class PagedDTO<T>(
val hasNext: Boolean,
val page: Int,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package plus.maa.backend.common.extensions

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import org.ktorm.entity.EntitySequence
import org.ktorm.entity.count
import org.ktorm.entity.drop
Expand All @@ -13,10 +14,12 @@ import org.ktorm.schema.SqlType
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import plus.maa.backend.common.serialization.defaultJson
import plus.maa.backend.controller.response.user.MaaUserInfo
import plus.maa.backend.repository.entity.MaaUser
import plus.maa.backend.repository.entity.UserEntity

@Serializable
data class PageResult<T>(
val data: List<T>,
val total: Long,
Expand Down Expand Up @@ -65,11 +68,11 @@ fun UserEntity.toMaaUserInfo(): MaaUserInfo {
)
}

private val objectMapper = jacksonObjectMapper()
infix fun ColumnDeclaring<*>.containsJson(list: Collection<*>): JsonbContainsExpression {
inline infix fun <reified T : Any> ColumnDeclaring<*>.containsJson(list: Collection<T>): JsonbContainsExpression {
val json = defaultJson
return JsonbContainsExpression(
left = this.asExpression(),
right = objectMapper.writeValueAsString(list),
right = json.encodeToString(list),
notFlag = false,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package plus.maa.backend.common.serialization

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import kotlinx.serialization.ExperimentalSerializationApi
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.Json
import kotlinx.serialization.modules.SerializersModule
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
import java.io.IOException
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter

private const val DEFAULT_DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"
private val defaultZone = ZoneId.of("UTC")
private val defaultDateTimeFormatter: DateTimeFormatter =
DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN).withZone(defaultZone)

@OptIn(ExperimentalSerializationApi::class)
val defaultJson = Json {
explicitNulls = false
ignoreUnknownKeys = true
this.serializersModule = SerializersModule {
contextual(LocalDateTime::class, LocalDateTimeAsStringSerializer)
contextual(Instant::class, InstantAsStringSerializer)
}
}

object LocalDateTimeAsStringSerializer : KSerializer<LocalDateTime> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): LocalDateTime {
val text = decoder.decodeString()
return try {
LocalDateTime.parse(text, defaultDateTimeFormatter)
} catch (ex: Exception) {
throw SerializationException("Cannot parse LocalDateTime: $text", ex)
}
}

override fun serialize(encoder: Encoder, value: LocalDateTime) {
encoder.encodeString(value.atZone(defaultZone).format(defaultDateTimeFormatter))
}
}

object InstantAsStringSerializer : KSerializer<Instant> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): Instant {
val text = decoder.decodeString()
return try {
Instant.from(defaultDateTimeFormatter.parse(text))
} catch (ex: Exception) {
throw SerializationException("Cannot parse Instant: $text", ex)
}
}

override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeString(defaultDateTimeFormatter.format(value))
}
}

@Configuration
class JacksonConfig {
@Bean
fun jsonCustomizer(): Jackson2ObjectMapperBuilderCustomizer =
Jackson2ObjectMapperBuilderCustomizer { builder: Jackson2ObjectMapperBuilder ->
val formatter = DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN).withZone(defaultZone)
builder.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
builder.serializers(LocalDateTimeSerializer(formatter))
}

class LocalDateTimeSerializer(
private val formatter: DateTimeFormatter,
) : StdSerializer<LocalDateTime>(LocalDateTime::class.java) {
@Throws(IOException::class)
override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.atZone(ZoneId.systemDefault()).format(formatter))
}
}
}

2 changes: 1 addition & 1 deletion src/main/kotlin/plus/maa/backend/common/utils/IpUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object IpUtil {
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ip != null && ip.length > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","))
ip = ip.substringBefore(",")
}
}
return ip
Expand Down
21 changes: 10 additions & 11 deletions src/main/kotlin/plus/maa/backend/config/HttpInterfaceConfig.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
package plus.maa.backend.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders
import org.springframework.http.codec.ClientCodecConfigurer
import org.springframework.http.codec.json.Jackson2JsonDecoder
import org.springframework.http.codec.json.Jackson2JsonEncoder
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
import org.springframework.http.codec.json.KotlinSerializationJsonEncoder
import org.springframework.web.reactive.function.client.ExchangeStrategies
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.support.WebClientAdapter
import org.springframework.web.service.invoker.HttpServiceProxyFactory
import plus.maa.backend.common.serialization.defaultJson
import plus.maa.backend.repository.GithubRepository

@Configuration
class HttpInterfaceConfig {
@OptIn(ExperimentalSerializationApi::class)
@Bean
fun githubRepository(): GithubRepository {
val mapper = Jackson2ObjectMapperBuilder.json()
.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
.build<ObjectMapper>()
val json = Json(defaultJson) {
namingStrategy = JsonNamingStrategy.SnakeCase
}

val client = WebClient.builder()
.baseUrl("https://api.github.com")
Expand All @@ -30,9 +31,7 @@ class HttpInterfaceConfig {
.builder()
.codecs { codecs: ClientCodecConfigurer ->
codecs.defaultCodecs()
.jackson2JsonEncoder(Jackson2JsonEncoder(mapper))
codecs.defaultCodecs()
.jackson2JsonDecoder(Jackson2JsonDecoder(mapper))
.kotlinSerializationJsonEncoder(KotlinSerializationJsonEncoder(json))
// 最大 20MB
codecs.defaultCodecs().maxInMemorySize(20 * 1024 * 1024)
}
Expand Down
37 changes: 0 additions & 37 deletions src/main/kotlin/plus/maa/backend/config/JacksonConfig.kt

This file was deleted.

7 changes: 0 additions & 7 deletions src/main/kotlin/plus/maa/backend/config/KtormConfig.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package plus.maa.backend.config

import com.fasterxml.jackson.databind.Module
import org.ktorm.database.Database
import org.ktorm.jackson.KtormModule
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import javax.sql.DataSource
Expand All @@ -16,9 +14,4 @@ class KtormConfig(val dataSource: DataSource) {
dialect = JsonbPostgreSqlDialect,
)
}

@Bean
fun ktormModule(): Module {
return KtormModule()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package plus.maa.backend.config

import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.PropertyNamingStrategies.LowerCamelCaseStrategy
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding
import org.springframework.context.annotation.Configuration
import plus.maa.backend.controller.request.copilot.CopilotDTO
Expand Down Expand Up @@ -30,7 +28,5 @@ import plus.maa.backend.repository.entity.gamedata.ArkZone
ArkTower::class,
ArkZone::class,
CopilotDTO::class,
PropertyNamingStrategies.SnakeCaseStrategy::class,
LowerCamelCaseStrategy::class,
)
class NativeReflectionConfig
25 changes: 25 additions & 0 deletions src/main/kotlin/plus/maa/backend/config/SerializationConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package plus.maa.backend.config

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import plus.maa.backend.common.serialization.defaultJson

@Configuration
class SerializationConfig : WebMvcConfigurer {

@OptIn(ExperimentalSerializationApi::class)
@Bean
fun kotlinJson(): Json = Json(defaultJson) {
namingStrategy = JsonNamingStrategy.SnakeCase
}

@Bean
fun kotlinSerializationHttpMessageConverter(json: Json) =
KotlinSerializationJsonHttpMessageConverter(json)

}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class SpringDocConfig(
@Bean
fun modelResolver(objectMapper: ObjectMapper) = ModelResolver(objectMapper)


companion object {
const val SECURITY_SCHEME_JWT: String = "Jwt"
const val SECURITY_SCHEME_API_KEY: String = "API_Key"
Expand Down
Loading