From c270ca9f6733669c6e7aef67b21d7ce81ff69652 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sun, 5 Oct 2025 22:03:58 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EB=AA=A8=EB=93=A0=20LocalDateTime,?= =?UTF-8?q?=20OffsetDateTime=20=ED=83=80=EC=9E=85=20Instant=20=EC=A0=84?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/web/config/JacksonConfig.kt | 44 +------------------ .../common/web/config/JacksonConfigTest.kt | 39 +--------------- .../src/pages/admin/AdminSchedulePage.tsx | 25 ++++++----- frontend/src/pages/admin/AdminStorePage.tsx | 19 ++++---- .../src/pages/admin/AdminThemeEditPage.tsx | 7 +-- 5 files changed, 30 insertions(+), 104 deletions(-) diff --git a/common/web/src/main/kotlin/com/sangdol/common/web/config/JacksonConfig.kt b/common/web/src/main/kotlin/com/sangdol/common/web/config/JacksonConfig.kt index bede0de2..a8843014 100644 --- a/common/web/src/main/kotlin/com/sangdol/common/web/config/JacksonConfig.kt +++ b/common/web/src/main/kotlin/com/sangdol/common/web/config/JacksonConfig.kt @@ -1,11 +1,8 @@ package com.sangdol.common.web.config -import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer 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.Configuration import java.time.LocalDate -import java.time.LocalDateTime import java.time.LocalTime -import java.time.OffsetDateTime -import java.time.ZoneId import java.time.format.DateTimeFormatter @Configuration class JacksonConfig { 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 = DateTimeFormatter.ofPattern("HH:mm") } @@ -35,9 +26,9 @@ class JacksonConfig { @Bean fun objectMapper(): ObjectMapper = ObjectMapper() .registerModule(javaTimeModule()) - .registerModule(dateTimeModule()) .registerModule(kotlinModule()) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) private fun javaTimeModule(): JavaTimeModule = JavaTimeModule() .addSerializer( @@ -56,35 +47,4 @@ class JacksonConfig { LocalTime::class.java, LocalTimeDeserializer(LOCAL_TIME_FORMATTER) ) 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() { - 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() { - override fun serialize( - value: OffsetDateTime, - gen: JsonGenerator, - serializers: SerializerProvider - ) { - gen.writeString(value.format(ISO_OFFSET_DATE_TIME_FORMATTER)) - } - } } diff --git a/common/web/src/test/kotlin/com/sangdol/common/web/config/JacksonConfigTest.kt b/common/web/src/test/kotlin/com/sangdol/common/web/config/JacksonConfigTest.kt index 19af86d1..037a78f5 100644 --- a/common/web/src/test/kotlin/com/sangdol/common/web/config/JacksonConfigTest.kt +++ b/common/web/src/test/kotlin/com/sangdol/common/web/config/JacksonConfigTest.kt @@ -7,10 +7,7 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import java.time.LocalDate -import java.time.LocalDateTime import java.time.LocalTime -import java.time.OffsetDateTime -import java.time.ZoneOffset class JacksonConfigTest : FunSpec({ @@ -55,38 +52,4 @@ class JacksonConfigTest : FunSpec({ }.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\"" - } - } -}) \ No newline at end of file +}) diff --git a/frontend/src/pages/admin/AdminSchedulePage.tsx b/frontend/src/pages/admin/AdminSchedulePage.tsx index 1e169177..98c995e7 100644 --- a/frontend/src/pages/admin/AdminSchedulePage.tsx +++ b/frontend/src/pages/admin/AdminSchedulePage.tsx @@ -1,5 +1,5 @@ -import { isLoginRequiredError } from '@_api/apiClient'; -import type { AuditInfo } from '@_api/common/commonTypes'; +import {isLoginRequiredError} from '@_api/apiClient'; +import type {AuditInfo} from '@_api/common/commonTypes'; import { createSchedule, deleteSchedule, @@ -7,15 +7,16 @@ import { fetchScheduleAudit, updateSchedule } from '@_api/schedule/scheduleAPI'; -import { type AdminScheduleSummaryResponse, ScheduleStatus, } from '@_api/schedule/scheduleTypes'; -import { getStores } from '@_api/store/storeAPI'; -import { type SimpleStoreResponse } from '@_api/store/storeTypes'; -import { fetchActiveThemes } from '@_api/theme/themeAPI'; -import { DifficultyKoreanMap, type SimpleActiveThemeResponse, type ThemeInfoResponse } from '@_api/theme/themeTypes'; -import { useAdminAuth } from '@_context/AdminAuthContext'; +import {type AdminScheduleSummaryResponse, ScheduleStatus,} from '@_api/schedule/scheduleTypes'; +import {getStores} from '@_api/store/storeAPI'; +import {type SimpleStoreResponse} from '@_api/store/storeTypes'; +import {fetchActiveThemes} from '@_api/theme/themeAPI'; +import {DifficultyKoreanMap, type SimpleActiveThemeResponse, type ThemeInfoResponse} from '@_api/theme/themeTypes'; +import {useAdminAuth} from '@_context/AdminAuthContext'; import '@_css/admin-schedule-page.css'; -import React, { Fragment, useEffect, useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import {formatDisplayDateTime} from '@_util/DateTimeFormatter'; +import React, {Fragment, useEffect, useState} from 'react'; +import {useLocation, useNavigate} from 'react-router-dom'; const getScheduleStatusText = (status: ScheduleStatus): string => { switch (status) { @@ -332,10 +333,10 @@ const AdminSchedulePage: React.FC = () => {

감사 정보

- 생성일: {new Date(detailedSchedules[schedule.id].audit!.createdAt).toLocaleString()} + 생성일: {formatDisplayDateTime(detailedSchedules[schedule.id].audit!.createdAt)}

- 수정일: {new Date(detailedSchedules[schedule.id].audit!.updatedAt).toLocaleString()} + 수정일: {formatDisplayDateTime(detailedSchedules[schedule.id].audit!.updatedAt)}

생성자: {detailedSchedules[schedule.id].audit!.createdBy.name}({detailedSchedules[schedule.id].audit!.createdBy.id}) diff --git a/frontend/src/pages/admin/AdminStorePage.tsx b/frontend/src/pages/admin/AdminStorePage.tsx index 9caa92fb..428f6a6f 100644 --- a/frontend/src/pages/admin/AdminStorePage.tsx +++ b/frontend/src/pages/admin/AdminStorePage.tsx @@ -1,17 +1,18 @@ -import { isLoginRequiredError } from '@_api/apiClient'; -import { fetchSidoList, fetchSigunguList } from '@_api/region/regionAPI'; -import type { SidoResponse, SigunguResponse } from '@_api/region/regionTypes'; -import { createStore, deleteStore, getStoreDetail, getStores, updateStore } from '@_api/store/storeAPI'; +import {isLoginRequiredError} from '@_api/apiClient'; +import {fetchSidoList, fetchSigunguList} from '@_api/region/regionAPI'; +import type {SidoResponse, SigunguResponse} from '@_api/region/regionTypes'; +import {createStore, deleteStore, getStoreDetail, getStores, updateStore} from '@_api/store/storeAPI'; import { type SimpleStoreResponse, type StoreDetailResponse, type StoreRegisterRequest, type UpdateStoreRequest } from '@_api/store/storeTypes'; -import { useAdminAuth } from '@_context/AdminAuthContext'; +import {useAdminAuth} from '@_context/AdminAuthContext'; import '@_css/admin-store-page.css'; -import React, { Fragment, useEffect, useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import {formatDisplayDateTime} from '@_util/DateTimeFormatter'; +import React, {Fragment, useEffect, useState} from 'react'; +import {useLocation, useNavigate} from 'react-router-dom'; const AdminStorePage: React.FC = () => { const [stores, setStores] = useState([]); @@ -297,10 +298,10 @@ const AdminStorePage: React.FC = () => { 코드: {detailedStores[store.id].region.code}

- 생성일: {new Date(detailedStores[store.id].audit.createdAt).toLocaleString()} + 생성일: {formatDisplayDateTime(detailedStores[store.id].audit.createdAt)}

- 수정일: {new Date(detailedStores[store.id].audit.updatedAt).toLocaleString()} + 수정일: {formatDisplayDateTime(detailedStores[store.id].audit.updatedAt)}

생성자: {detailedStores[store.id].audit.createdBy.name}({detailedStores[store.id].audit.createdBy.id}) diff --git a/frontend/src/pages/admin/AdminThemeEditPage.tsx b/frontend/src/pages/admin/AdminThemeEditPage.tsx index 0e6b6797..f258bc7a 100644 --- a/frontend/src/pages/admin/AdminThemeEditPage.tsx +++ b/frontend/src/pages/admin/AdminThemeEditPage.tsx @@ -9,7 +9,8 @@ import { import React, {useEffect, useState} from 'react'; import {useLocation, useNavigate, useParams} from 'react-router-dom'; 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 { name: string; @@ -256,8 +257,8 @@ const AdminThemeEditPage: React.FC = () => {

감사 정보

-

생성일: {new Date(auditInfo.createdAt).toLocaleString()}

-

수정일: {new Date(auditInfo.updatedAt).toLocaleString()}

+

생성일: {formatDisplayDateTime(auditInfo.createdAt)}

+

수정일: {formatDisplayDateTime(auditInfo.updatedAt)}

생성자: {auditInfo.createdBy.name}

수정자: {auditInfo.updatedBy.name}