generated from pricelees/issue-pr-template
refactor: 모든 LocalDateTime, OffsetDateTime 타입 Instant 전환
This commit is contained in:
parent
e93d8de6cc
commit
c270ca9f67
@ -1,11 +1,8 @@
|
|||||||
package com.sangdol.common.web.config
|
package com.sangdol.common.web.config
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonGenerator
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer
|
||||||
@ -15,19 +12,13 @@ import com.fasterxml.jackson.module.kotlin.kotlinModule
|
|||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
class JacksonConfig {
|
class JacksonConfig {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val ISO_OFFSET_DATE_TIME_FORMATTER: DateTimeFormatter =
|
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX")
|
|
||||||
|
|
||||||
private val LOCAL_TIME_FORMATTER: DateTimeFormatter =
|
private val LOCAL_TIME_FORMATTER: DateTimeFormatter =
|
||||||
DateTimeFormatter.ofPattern("HH:mm")
|
DateTimeFormatter.ofPattern("HH:mm")
|
||||||
}
|
}
|
||||||
@ -35,9 +26,9 @@ class JacksonConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
fun objectMapper(): ObjectMapper = ObjectMapper()
|
fun objectMapper(): ObjectMapper = ObjectMapper()
|
||||||
.registerModule(javaTimeModule())
|
.registerModule(javaTimeModule())
|
||||||
.registerModule(dateTimeModule())
|
|
||||||
.registerModule(kotlinModule())
|
.registerModule(kotlinModule())
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||||
|
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
|
||||||
|
|
||||||
private fun javaTimeModule(): JavaTimeModule = JavaTimeModule()
|
private fun javaTimeModule(): JavaTimeModule = JavaTimeModule()
|
||||||
.addSerializer(
|
.addSerializer(
|
||||||
@ -56,35 +47,4 @@ class JacksonConfig {
|
|||||||
LocalTime::class.java,
|
LocalTime::class.java,
|
||||||
LocalTimeDeserializer(LOCAL_TIME_FORMATTER)
|
LocalTimeDeserializer(LOCAL_TIME_FORMATTER)
|
||||||
) as JavaTimeModule
|
) as JavaTimeModule
|
||||||
|
|
||||||
private fun dateTimeModule(): SimpleModule {
|
|
||||||
val simpleModule = SimpleModule()
|
|
||||||
simpleModule.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer())
|
|
||||||
simpleModule.addSerializer(OffsetDateTime::class.java, OffsetDateTimeSerializer())
|
|
||||||
return simpleModule
|
|
||||||
}
|
|
||||||
|
|
||||||
class LocalDateTimeSerializer : JsonSerializer<LocalDateTime>() {
|
|
||||||
override fun serialize(
|
|
||||||
value: LocalDateTime,
|
|
||||||
gen: JsonGenerator,
|
|
||||||
serializers: SerializerProvider
|
|
||||||
) {
|
|
||||||
value.atZone(ZoneId.systemDefault())
|
|
||||||
.toOffsetDateTime()
|
|
||||||
.also {
|
|
||||||
gen.writeString(it.format(ISO_OFFSET_DATE_TIME_FORMATTER))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OffsetDateTimeSerializer : JsonSerializer<OffsetDateTime>() {
|
|
||||||
override fun serialize(
|
|
||||||
value: OffsetDateTime,
|
|
||||||
gen: JsonGenerator,
|
|
||||||
serializers: SerializerProvider
|
|
||||||
) {
|
|
||||||
gen.writeString(value.format(ISO_OFFSET_DATE_TIME_FORMATTER))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,10 +7,7 @@ import io.kotest.core.spec.style.FunSpec
|
|||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.kotest.matchers.string.shouldContain
|
import io.kotest.matchers.string.shouldContain
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
|
|
||||||
class JacksonConfigTest : FunSpec({
|
class JacksonConfigTest : FunSpec({
|
||||||
|
|
||||||
@ -55,38 +52,4 @@ class JacksonConfigTest : FunSpec({
|
|||||||
}.message shouldContain "Text '$hour:$minute:$sec' could not be parsed"
|
}.message shouldContain "Text '$hour:$minute:$sec' could not be parsed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context("Long 타입은 문자열로 (역)직렬화된다.") {
|
|
||||||
val number = 1234567890L
|
|
||||||
val serialized: String = objectMapper.writeValueAsString(number)
|
|
||||||
val deserialized: Long = objectMapper.readValue(serialized, Long::class.java)
|
|
||||||
|
|
||||||
test("Long 직렬화") {
|
|
||||||
serialized shouldBe "$number"
|
|
||||||
}
|
|
||||||
|
|
||||||
test("Long 역직렬화") {
|
|
||||||
deserialized shouldBe number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("OffsetDateTime은 ISO 8601 형식으로 직렬화된다.") {
|
|
||||||
val date = LocalDate.of(2025, 7, 14)
|
|
||||||
val time = LocalTime.of(12, 30, 0)
|
|
||||||
val dateTime = OffsetDateTime.of(date, time, ZoneOffset.ofHours(9))
|
|
||||||
val serialized: String = objectMapper.writeValueAsString(dateTime)
|
|
||||||
|
|
||||||
test("OffsetDateTime 직렬화") {
|
|
||||||
serialized shouldBe "\"2025-07-14T12:30:00+09:00\""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("LocalDateTime은 ISO 8601 형식으로 직렬화된다.") {
|
|
||||||
val dateTime = LocalDateTime.of(2025, 7, 14, 12, 30, 0)
|
|
||||||
val serialized: String = objectMapper.writeValueAsString(dateTime)
|
|
||||||
|
|
||||||
test("LocalDateTime 직렬화") {
|
|
||||||
serialized shouldBe "\"2025-07-14T12:30:00+09:00\""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { isLoginRequiredError } from '@_api/apiClient';
|
import {isLoginRequiredError} from '@_api/apiClient';
|
||||||
import type { AuditInfo } from '@_api/common/commonTypes';
|
import type {AuditInfo} from '@_api/common/commonTypes';
|
||||||
import {
|
import {
|
||||||
createSchedule,
|
createSchedule,
|
||||||
deleteSchedule,
|
deleteSchedule,
|
||||||
@ -7,15 +7,16 @@ import {
|
|||||||
fetchScheduleAudit,
|
fetchScheduleAudit,
|
||||||
updateSchedule
|
updateSchedule
|
||||||
} from '@_api/schedule/scheduleAPI';
|
} from '@_api/schedule/scheduleAPI';
|
||||||
import { type AdminScheduleSummaryResponse, ScheduleStatus, } from '@_api/schedule/scheduleTypes';
|
import {type AdminScheduleSummaryResponse, ScheduleStatus,} from '@_api/schedule/scheduleTypes';
|
||||||
import { getStores } from '@_api/store/storeAPI';
|
import {getStores} from '@_api/store/storeAPI';
|
||||||
import { type SimpleStoreResponse } from '@_api/store/storeTypes';
|
import {type SimpleStoreResponse} from '@_api/store/storeTypes';
|
||||||
import { fetchActiveThemes } from '@_api/theme/themeAPI';
|
import {fetchActiveThemes} from '@_api/theme/themeAPI';
|
||||||
import { DifficultyKoreanMap, type SimpleActiveThemeResponse, type ThemeInfoResponse } from '@_api/theme/themeTypes';
|
import {DifficultyKoreanMap, type SimpleActiveThemeResponse, type ThemeInfoResponse} from '@_api/theme/themeTypes';
|
||||||
import { useAdminAuth } from '@_context/AdminAuthContext';
|
import {useAdminAuth} from '@_context/AdminAuthContext';
|
||||||
import '@_css/admin-schedule-page.css';
|
import '@_css/admin-schedule-page.css';
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
import {formatDisplayDateTime} from '@_util/DateTimeFormatter';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import React, {Fragment, useEffect, useState} from 'react';
|
||||||
|
import {useLocation, useNavigate} from 'react-router-dom';
|
||||||
|
|
||||||
const getScheduleStatusText = (status: ScheduleStatus): string => {
|
const getScheduleStatusText = (status: ScheduleStatus): string => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@ -332,10 +333,10 @@ const AdminSchedulePage: React.FC = () => {
|
|||||||
<h4 className="audit-title">감사 정보</h4>
|
<h4 className="audit-title">감사 정보</h4>
|
||||||
<div className="audit-body">
|
<div className="audit-body">
|
||||||
<p>
|
<p>
|
||||||
<strong>생성일:</strong> {new Date(detailedSchedules[schedule.id].audit!.createdAt).toLocaleString()}
|
<strong>생성일:</strong> {formatDisplayDateTime(detailedSchedules[schedule.id].audit!.createdAt)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>수정일:</strong> {new Date(detailedSchedules[schedule.id].audit!.updatedAt).toLocaleString()}
|
<strong>수정일:</strong> {formatDisplayDateTime(detailedSchedules[schedule.id].audit!.updatedAt)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>생성자:</strong> {detailedSchedules[schedule.id].audit!.createdBy.name}({detailedSchedules[schedule.id].audit!.createdBy.id})
|
<strong>생성자:</strong> {detailedSchedules[schedule.id].audit!.createdBy.name}({detailedSchedules[schedule.id].audit!.createdBy.id})
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
import { isLoginRequiredError } from '@_api/apiClient';
|
import {isLoginRequiredError} from '@_api/apiClient';
|
||||||
import { fetchSidoList, fetchSigunguList } from '@_api/region/regionAPI';
|
import {fetchSidoList, fetchSigunguList} from '@_api/region/regionAPI';
|
||||||
import type { SidoResponse, SigunguResponse } from '@_api/region/regionTypes';
|
import type {SidoResponse, SigunguResponse} from '@_api/region/regionTypes';
|
||||||
import { createStore, deleteStore, getStoreDetail, getStores, updateStore } from '@_api/store/storeAPI';
|
import {createStore, deleteStore, getStoreDetail, getStores, updateStore} from '@_api/store/storeAPI';
|
||||||
import {
|
import {
|
||||||
type SimpleStoreResponse,
|
type SimpleStoreResponse,
|
||||||
type StoreDetailResponse,
|
type StoreDetailResponse,
|
||||||
type StoreRegisterRequest,
|
type StoreRegisterRequest,
|
||||||
type UpdateStoreRequest
|
type UpdateStoreRequest
|
||||||
} from '@_api/store/storeTypes';
|
} from '@_api/store/storeTypes';
|
||||||
import { useAdminAuth } from '@_context/AdminAuthContext';
|
import {useAdminAuth} from '@_context/AdminAuthContext';
|
||||||
import '@_css/admin-store-page.css';
|
import '@_css/admin-store-page.css';
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
import {formatDisplayDateTime} from '@_util/DateTimeFormatter';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import React, {Fragment, useEffect, useState} from 'react';
|
||||||
|
import {useLocation, useNavigate} from 'react-router-dom';
|
||||||
|
|
||||||
const AdminStorePage: React.FC = () => {
|
const AdminStorePage: React.FC = () => {
|
||||||
const [stores, setStores] = useState<SimpleStoreResponse[]>([]);
|
const [stores, setStores] = useState<SimpleStoreResponse[]>([]);
|
||||||
@ -297,10 +298,10 @@ const AdminStorePage: React.FC = () => {
|
|||||||
코드:</strong> {detailedStores[store.id].region.code}
|
코드:</strong> {detailedStores[store.id].region.code}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>생성일:</strong> {new Date(detailedStores[store.id].audit.createdAt).toLocaleString()}
|
<strong>생성일:</strong> {formatDisplayDateTime(detailedStores[store.id].audit.createdAt)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>수정일:</strong> {new Date(detailedStores[store.id].audit.updatedAt).toLocaleString()}
|
<strong>수정일:</strong> {formatDisplayDateTime(detailedStores[store.id].audit.updatedAt)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>생성자:</strong> {detailedStores[store.id].audit.createdBy.name}({detailedStores[store.id].audit.createdBy.id})
|
<strong>생성자:</strong> {detailedStores[store.id].audit.createdBy.name}({detailedStores[store.id].audit.createdBy.id})
|
||||||
|
|||||||
@ -9,7 +9,8 @@ import {
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {useLocation, useNavigate, useParams} from 'react-router-dom';
|
import {useLocation, useNavigate, useParams} from 'react-router-dom';
|
||||||
import '@_css/admin-theme-edit-page.css';
|
import '@_css/admin-theme-edit-page.css';
|
||||||
import type { AuditInfo } from '@_api/common/commonTypes';
|
import type {AuditInfo} from '@_api/common/commonTypes';
|
||||||
|
import {formatDisplayDateTime} from '@_util/DateTimeFormatter';
|
||||||
|
|
||||||
interface ThemeFormData {
|
interface ThemeFormData {
|
||||||
name: string;
|
name: string;
|
||||||
@ -256,8 +257,8 @@ const AdminThemeEditPage: React.FC = () => {
|
|||||||
<div className="audit-info">
|
<div className="audit-info">
|
||||||
<h4 className="audit-title">감사 정보</h4>
|
<h4 className="audit-title">감사 정보</h4>
|
||||||
<div className="audit-body">
|
<div className="audit-body">
|
||||||
<p><strong>생성일:</strong> {new Date(auditInfo.createdAt).toLocaleString()}</p>
|
<p><strong>생성일:</strong> {formatDisplayDateTime(auditInfo.createdAt)}</p>
|
||||||
<p><strong>수정일:</strong> {new Date(auditInfo.updatedAt).toLocaleString()}</p>
|
<p><strong>수정일:</strong> {formatDisplayDateTime(auditInfo.updatedAt)}</p>
|
||||||
<p><strong>생성자:</strong> {auditInfo.createdBy.name}</p>
|
<p><strong>생성자:</strong> {auditInfo.createdBy.name}</p>
|
||||||
<p><strong>수정자:</strong> {auditInfo.updatedBy.name}</p>
|
<p><strong>수정자:</strong> {auditInfo.updatedBy.name}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user