기술 스택
iFlex 는 NeoSQL 로 생성되는 신규 프로젝트의 템플릿입니다. 템플릿이지만 그대로 실행되도록 구성되어 있어, 생성 직후부터 일반 프로젝트와 동일하게 개발·테스트할 수 있습니다.
처음 오셨다면
버전 표만 훑고 개발 환경 설정 으로 넘어가세요. 아키텍처는 실제로 코드를 작성할 때 Unvus Core 문서에서 자세히 다룹니다.
개발 환경
| 도구 | 버전 | 비고 |
|---|---|---|
| JDK | 21 | Gradle toolchain 으로 고정 (JavaLanguageVersion.of(21)) |
| Gradle | 8.14 | wrapper 포함 — 별도 설치 불필요 (./gradlew) |
| Node.js | 22.22+ 또는 24.12+ | 두 프론트엔드 공통 (^22.22.0 || >=24.12.0) |
Node 22.22 미만은 동작하지 않습니다
@quasar/app-vite 3 이 ^22.22.0 이상을 요구합니다. Node 20 은 지원 대상에서 빠졌습니다.
nvm install lts/jod # Node 22.x
node -v # v22.22.0 이상인지 확인JDK 는 21 이어야 합니다
Spring Boot 4.x 는 JDK 17 을 지원하지 않습니다. sdkman 으로 멀티 JDK 환경을 구성해 프로젝트별로 전환하는 것을 권장합니다.
sdk install java 21.0.5-tem
sdk use java 21.0.5-tem실행 시 JVM 옵션이 필요합니다
Spring Boot 실행에는 --add-opens 와 -Duser.timezone=UTC 가 필수입니다. UTC 를 지정하지 않으면 기동이 실패합니다 — 프로젝트는 날짜/시간을 UTC 로 고정해 다루기 때문입니다. 자세한 내용은 개발 환경 설정 을 보세요.
서버 사이드
| 라이브러리 | 버전 |
|---|---|
| Spring Boot | 4.0.3 |
| Spring Cloud | 2024.0.0 |
| Spring Security | Spring Boot BOM 관리 (JWT / OAuth2 Resource Server) |
| MyBatis Dynamic SQL | 2.0.0 |
| MyBatis Spring Boot Starter | 4.0.1 |
| springdoc-openapi | 3.0.3 |
| Apache POI | 5.5.1 (엑셀) |
| Apache Tika | 3.3.1 (파일 타입 판별) |
| P6Spy | 2.0.0 (SQL 로깅) |
| Lombok | 1.18.38 |
데이터 접근은 MyBatis 로 합니다. ORM 은 쓰지 않습니다. SQL 은 Dynamic SQL 로 조립하고, 결과 매핑(resultMap)은 mapper XML 에 둡니다 → MyBatis Dynamic SQL
트랜잭션은 DataSourceTransactionManager 가 커넥션 단위로 관리하며, MyBatis 매퍼 호출이 @Transactional 경계에 참여합니다. 세션은 Redis, 캐시는 EhCache 를 사용합니다.
지원 DBMS
MariaDB / MySQL / Oracle / PostgreSQL / SQL Server. DDL 은 neo-sql/{dbms}/ 에 DBMS 별로 준비되어 있고, 키 생성 전략은 런타임에 자동 분기합니다 (→ MyBatis Dynamic SQL).
프론트엔드
두 프론트엔드는 공통 의존성 버전을 완전히 일치시켜 운영합니다. 한쪽만 올라가서 생기는 동작 차이를 없애기 위해서입니다.
공통 (버전 동일)
| 라이브러리 | 버전 |
|---|---|
| Vue | 3.5 |
| Quasar | 2.23 |
| @quasar/app-vite | 3.2 (내부 Vite 8) |
| Pinia | 4.0 |
| Vue Router | 5.2 (hash 모드) |
| Element Plus | 2.14 |
| TypeScript | 5.9 |
| ESLint | 10 |
| axios / dayjs / vue-final-modal | 1.18 / 1.11 / 4.5 |
프로젝트별 차이
| cms/frontend (관리자) | channel-vue/frontend (사용자) | |
|---|---|---|
| 렌더링 | CSR | SSR (Express) |
| 전용 라이브러리 | AG Grid, CodeMirror, SunEditor, CKEditor, ApexCharts, Highcharts | vue-i18n |
렌더링 방식과 화면 성격에 따른 전용 라이브러리만 다르고, 공통 스택은 동일합니다.
- 컴포넌트는 Composition API +
<script setup>로 작성합니다. - 커스텀 컴포넌트는
nv-접두사를 씁니다 (nv-button,nv-input…). - 자세한 규칙은 Vue 3 + TypeScript 참고.
모듈 구조
이건 iFlex 저장소의 구조입니다
iFlex 는 템플릿입니다. NeoSQL 로 실제 고객사 프로젝트를 생성하면 모듈 이름이 바뀌고, 필요 없는 모듈은 아예 빠집니다. 아래 이름을 고정된 것으로 여기지 마세요.
iflex/
├── core/ 공유 라이브러리 (bootJar 비활성)
├── channel/ PC 웹사이트 (Thymeleaf, WAR)
├── channel-vue/
│ ├── backend/ 모바일·최신 웹 REST API
│ └── frontend/ Quasar SSR
├── cms/
│ ├── backend/ 관리자 REST API
│ └── frontend/ 관리자 UI (CSR)
├── batch/ 배치·스케줄러
├── neo-config/ NeoSQL 프로젝트 생성 설정
├── neo-sql/ DBMS 별 SQL 스크립트
└── docker/ 환경별 docker-compose모든 백엔드 모듈은 core 를 의존합니다. 업무 로직·엔터티·플랫폼 코드는 전부 core 에 있고, 각 모듈은 REST 표면과 설정만 갖습니다.
프로젝트 생성 후에는 어떻게 되나
neo-config/neo.conf.js 가 모듈 구성을 정의합니다. 생성 시 선택한 것만, 새 이름으로 만들어집니다.
| 저장소 | 생성 후 기본 이름 | 개수 | 패키지 |
|---|---|---|---|
core | core | 필수 1개 | {base}.core |
cms | cms | 0~N개 | {base}.cms |
channel | pc-web | 0~N개 | {base}.web.pc |
channel-vue | mobile-web | 0~N개 | {base}.web.mobile |
batch | batch | 0~1개 | {base}.batch |
읽는 방법이 셋 있습니다.
- 이름이 바뀝니다 —
channel은pc-web,channel-vue는mobile-web이 기본이고, 생성 시 더 바꿀 수 있습니다 - 빠질 수 있습니다 —
core를 뺀 전부가 최소 0개입니다. 배치가 필요 없으면batch모듈뿐 아니라modules/batch·platform/batch소스와 관련 SQL 까지 함께 제외됩니다 - 여러 개일 수 있습니다 —
cms·pc-web·mobile-web은 상한이 없습니다. 운영자용과 협력사용 CMS 를 따로 두는 식으로 같은 종류를 여러 벌 만들 수 있습니다
패키지도 com.unvus.iflex.* 에서 고객사 basePackage 로, 테이블 접두어도 nv_ 에서 지정한 값으로 치환됩니다.
생성 전용 디렉토리는 어떻게 되나
| 디렉토리 | 생성된 프로젝트에 |
|---|---|
neo-config/ | 없음 — 템플릿을 만들기 위한 설정이라 결과물에는 넘어가지 않습니다 |
docker/ | 없음 |
neo-sql/ | 있음 — 단, 선택한 DBMS 하나만 남습니다 (neo-sql/mysql/ → neo-sql/) |
.neosql/ | 있음 — 이후 엔터티 생성에 계속 쓰는 메타데이터입니다 |
neo-sql 은 4개 DBMS 스크립트를 다 들고 있다가, 생성 시 고른 하나만 남기고 테이블 접두어까지 치환해 넘어갑니다.
이 문서의 나머지 설명은 저장소 기준 이름(cms, channel-vue …)을 씁니다. 실제 프로젝트에서는 해당 위치의 모듈로 바꿔 읽으세요.
core 의 구조
core 는 두 층으로 나뉩니다. 이 구분이 코드를 읽는 가장 중요한 단서입니다.
core/src/main/java/com/unvus/iflex/core/
├── platform/ 프레임워크 — 업무와 무관한 재사용 기반
└── modules/ 업무 — 게시판, 회원, 배너, 메시지 …platform 패키지
업무 코드가 기대는 기반입니다. 신규 기능을 만들 때 여기 있는 것을 먼저 찾아 쓰세요.
| 패키지 | 역할 | 관련 문서 |
|---|---|---|
query | 검색 조건 트리(NvQuery), 스키마 선언, SQL 조건 조립 | 검색 조건 |
pagination | 페이징·정렬 실행(InPagination), 반복 조회(DbIterator) | 목록과 페이징 |
support | 제네릭 리포지토리·서비스(NvRepositoryDynamic), 키 전략 | MyBatis Dynamic SQL |
domain | 감사 인터페이스(AbstractAuditingEntity), EnumCode, 보안 주체 | |
excel | 엑셀 생성·읽기(ExcelBuilder) | |
firo | 파일 업로드 플랫폼 | Firo |
messaging | 메일·푸시·SMS 발송 | |
audit | 감사 로그·액션 로그 | |
web | 웹 유틸, 인터셉터 | |
util | 날짜·타임존·컬렉션 등 공용 유틸 | 유틸리티 |
왜 platform 과 modules 를 나누나
platform 은 모든 고객사 프로젝트가 공유하는 코드입니다. 여기를 고치면 전 프로젝트에 영향이 갑니다. 반대로 modules 는 프로젝트마다 달라지는 업무 코드입니다. 업무 요구사항을 구현할 때 platform 을 고치고 있다면, 대개는 잘못된 위치에 코드를 쓰고 있는 것입니다.
modules 패키지
업무 도메인 하나가 한 패키지입니다. 내부 구조는 모든 모듈이 동일합니다.
modules/board/
├── entity/ Board.java + support/BoardDsl.java
├── dto/ BoardDto.java, BoardSearchSchema.java
├── repository/ BoardRepository.java
└── service/ BoardService.java| 파일 | 역할 |
|---|---|
{X}.java | 엔터티 (테이블 1:1) |
{X}Dsl.java | 컬럼 참조용 DSL — 컬럼명을 문자열로 쓰지 않게 해주는 타입 안전 장치 |
{X}Dto.java | API 응답 DTO (엔터티를 직접 노출하지 않습니다) |
{X}SearchSchema.java | 검색 가능 필드 화이트리스트 |
{X}Repository.java | 데이터 접근 |
{X}Service.java | 업무 로직 |
날짜/시간 규약
프로젝트 전체가 하나의 규칙을 따릅니다. 처음 합류하면 가장 먼저 알아야 할 규약입니다.
| 상황 | 사용 타입 |
|---|---|
| 시각 (특정 순간) | Instant — 항상 UTC |
| 날짜 (달력상 하루) | LocalDate |
| 금지 | ❌ LocalDateTime — 타임존이 없어 의미가 모호합니다 |
- 저장·전송·서버 내부 연산은 전부 UTC 입니다. 와이어 포맷은 ISO-8601 (
2026-07-25T04:12:33Z). - 사용자 타임존은 화면에 보여줄 때만 적용합니다.
- 인자 없는
now()는 금지입니다 (Instant.now()는 UTC 라 안전,LocalDate.now()는 서버 로케일에 의존). 업무상 "오늘" 은TimeZones.siteDefault()를 기준으로 구합니다.
Instant createdDt = Instant.now(); // ✅ 시각
LocalDate today = LocalDate.now(TimeZones.siteDefault()); // ✅ 업무상 오늘
LocalDateTime bad = LocalDateTime.now(); // ❌ 금지 (테스트가 잡습니다)프론트엔드는 useFormat / formatDt 로 표시 시점에 변환합니다.