유틸리티
core/platform/util 에 있는 공용 도구들입니다. 새 유틸을 만들기 전에 여기부터 확인하세요 — 이미 있는 경우가 많습니다.
날짜/시간
프로젝트 전체가 하나의 규칙을 따릅니다. 이 규약을 모르면 시차 버그가 납니다.
| 상황 | 타입 |
|---|---|
| 시각 (특정 순간) | Instant — 항상 UTC |
| 날짜 (달력상 하루) | LocalDate |
| 금지 | ❌ LocalDateTime |
LocalDateTime 을 쓰지 마세요
타임존 정보가 없어 "언제인지" 가 모호합니다. 저장할 때와 읽을 때 서버 로케일이 다르면 시각이 어긋납니다. 아키텍처 테스트(DateTimeArchitectureTest)가 사용을 막습니다.
TimeZones
타임존 정책의 단일 진입점입니다. 직접 ZoneId.of("Asia/Seoul") 를 쓰지 마세요.
TimeZones.UTC // 저장·전송·내부 연산 기준 (항상 UTC)
TimeZones.siteDefault() // 사이트 업무 타임존 (기본 Asia/Seoul)
TimeZones.effective() // 요청 사용자 기준 — 계정 타임존 → 없으면 사이트 기본
TimeZones.of("Asia/Tokyo") // 문자열 → ZoneId. 유효하지 않으면 null| 메서드 | 언제 쓰나 |
|---|---|
UTC | 저장·전송. Instant 를 쓰면 사실상 자동 |
siteDefault() | 업무상 "오늘", 배치 일 경계, cron 스케줄 |
effective() | 사용자에게 보여줄 값을 만들 때 |
// ✅ 업무상 오늘 (사이트 타임존 기준 자정~자정)
LocalDate today = LocalDate.now(TimeZones.siteDefault());
// ✅ 사용자 화면에 표시할 문자열
String shown = DateTools.format(order.getCreatedDt(), "yyyy-MM-dd HH:mm", TimeZones.effective());
// ❌ 서버 로케일에 의존 — 인자 없는 now() 는 금지
LocalDate today = LocalDate.now();인자 없는 now() 는 금지입니다
Instant.now() 는 UTC 라 안전하지만, LocalDate.now() / LocalDateTime.now() 는 서버 OS 의 타임존을 따릅니다. 개발 PC 와 운영 서버가 다르면 결과가 달라집니다. 테스트가 이를 강제합니다.
siteDefault 는 application.timezone.default 설정으로 기동 시 결정됩니다.
DateTools
포맷팅과 타입 변환을 담당합니다.
// 포맷 (타임존 지정 가능)
DateTools.format(instant, "yyyy-MM-dd HH:mm:ss"); // 기본 타임존
DateTools.format(instant, "yyyy-MM-dd HH:mm:ss", TimeZones.effective());
DateTools.format(localDate, "yyyy.MM.dd");
// 타입 변환
Instant i = DateTools.convert(localDate, ConvertTo.INSTANT);
LocalDate d = DateTools.convert(instant, ConvertTo.LOCAL_DATE);
Long m = DateTools.convert(instant, ConvertTo.LONG);변환 대상(ConvertTo): LONG · DATE · LOCAL_DATE · LOCAL_DATE_TIME · ZONED_DATE_TIME · INSTANT
표시 변환은 프론트에서
서버는 항상 ISO-8601 UTC(2026-07-25T04:12:33Z)로 내려줍니다. 표시 포맷은 프론트의 useFormat / formatDt 가 담당합니다 — 서버에서 미리 포맷하지 마세요.
Map 계열
FieldMap
LinkedHashMap<String, Object> 를 상속한 순서 보존 맵입니다. 동적 파라미터나 임시 응답 구조에 씁니다.
FieldMap result = new FieldMap();
result.put("cdnUrl", cdnUrl);
result.put("domainMap", domains);
FieldMap one = FieldMap.of("key", value); // 단건 생성JsonMap
FieldMap 을 상속하며, DB 의 JSON 컬럼에 매핑되는 타입입니다.
// 엔터티 필드
private JsonMap config;
// Dsl — typeHandler 를 지정해야 합니다
public final SqlColumn<JsonMap> config =
column("bd_config", JDBCType.LONGVARCHAR,
"com.unvus.iflex.core.config.mybatis.type.VarcharJsonTypeHandler");
// 생성
JsonMap.of(Map.of("theme", "dark"));
JsonMap.of("theme", "dark");JSON 컬럼은 Dsl 에 typeHandler 를 반드시 지정하세요
빠뜨리면 저장 시 toString() 결과가 들어가거나 500 에러가 납니다. XML resultMap 에도 같은 typeHandler 를 지정해야 읽을 때 파싱됩니다.
MapUtil
MapUtil.splitKey(param); // "a.b" 형태 키를 중첩 맵으로 분해컬렉션
| 클래스 | 용도 |
|---|---|
ListComparisonUtil | 두 리스트의 추가·삭제·유지 항목 비교 (자식 목록 동기화에 유용) |
MappedList | 키로 인덱싱된 리스트 |
ListIterator | 리스트 순회 보조 |
LfuCache<K,V> | LFU 캐시 — 용량·TTL 지정 가능 |
// 예: 첨부 뷰의 negative 캐시 (최대 1만 건, TTL 300초)
private static LfuCache<Object, Object> nullCache = new LfuCache<>(10000L, 300L, true);기타
| 클래스 | 용도 |
|---|---|
RandomUtil | 임의 문자열·비밀번호 생성 (generatePassword()) |
JsonUtil | JSON 직렬화·역직렬화 보조 |
BindUtil | 객체 바인딩 보조 |
유틸을 새로 만들 때
위치 판단 기준
| 성격 | 위치 |
|---|---|
| 업무와 무관, 모든 프로젝트가 쓸 것 | platform/util |
| 특정 도메인 전용 | 해당 modules/{도메인}/ 안 |
| 웹 요청과 관련 | platform/web/util |
platform 은 모든 고객사 프로젝트가 공유합니다. 업무 로직이 섞인 유틸을 여기 두면 다른 프로젝트에 불필요한 코드가 따라갑니다.
관련 문서
- 기술 스택 — 날짜/시간 규약 요약