feat: WTM 멀티프로젝트 플랫폼 구축 (BE + FE 전체 구현)

Phase 0: wbx-spring-core 라이브러리 전환
- java-library 플러그인, WbxAutoConfiguration, Admin 조건부 활성화
- 루트 settings.gradle + build.gradle (멀티모듈)

Phase 1: wtm-api 모듈 생성
- 23개 JPA Entity, 14개 Controller, 79개 API 엔드포인트
- Flyway V100~V107 MySQL 마이그레이션
- TimesheetRuleEngine, TimesheetApprovalHandler, P6WbsParser

Phase 2: wtm-frontend (Vue 3 + PrimeVue 4)
- 10개 도메인 모듈, 17개 View, 5개 서브컴포넌트
- 반응형 레이아웃 (AppLayout, AppSidebar, AppTopbar)
- BaseCrudTable, BaseFormDialog, BasePageHeader 표준 컴포넌트
- JWT 인터셉터, 역할 기반 메뉴 필터링

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
이 Commit은 다음에 포함되어 있습니다:
2026-03-25 21:01:43 +09:00
부모 783865266b
커밋 df723f1d59
533개의 변경된 파일15528개의 추가작업 그리고 154개의 파일을 삭제

바이너리 파일은 표시되지 않습니다.

바이너리 파일은 표시되지 않습니다.

바이너리 파일은 표시되지 않습니다.

파일 보기

@@ -0,0 +1,838 @@
"""
한화오션 EPU WTM 시스템 설계서 PPT 생성
요구사항정의 → To-Be → 시스템구성도 → 기능정의 → 시스템설계
"""
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
import os
OUTPUT = os.path.join(os.path.dirname(__file__), "한화오션_EPU_WTM_시스템설계서.pptx")
# 색상
PRIMARY = RGBColor(30, 60, 120)
ACCENT = RGBColor(0, 120, 200)
DARK = RGBColor(40, 40, 40)
BODY = RGBColor(80, 80, 80)
WHITE = RGBColor(255, 255, 255)
LIGHT_BG = RGBColor(240, 245, 255)
GREEN_BG = RGBColor(220, 240, 220)
ORANGE_BG = RGBColor(255, 240, 220)
RED_ACCENT = RGBColor(200, 60, 60)
def add_box(slide, left, top, width, height, text, fill_color=LIGHT_BG, font_color=PRIMARY, font_size=10, bold=True):
shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height)
shape.fill.solid()
shape.fill.fore_color.rgb = fill_color
shape.line.color.rgb = PRIMARY
shape.line.width = Pt(1)
tf = shape.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = text
p.font.size = Pt(font_size)
p.font.color.rgb = font_color
p.font.bold = bold
p.alignment = PP_ALIGN.CENTER
tf.paragraphs[0].space_before = Pt(2)
return shape
def add_textbox(slide, left, top, width, height, text, font_size=10, color=BODY, bold=False, align=PP_ALIGN.LEFT):
txBox = slide.shapes.add_textbox(left, top, width, height)
tf = txBox.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = text
p.font.size = Pt(font_size)
p.font.color.rgb = color
p.font.bold = bold
p.alignment = align
return txBox
def add_multiline_box(slide, left, top, width, height, title, items, fill_color=LIGHT_BG):
shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height)
shape.fill.solid()
shape.fill.fore_color.rgb = fill_color
shape.line.color.rgb = PRIMARY
shape.line.width = Pt(1)
tf = shape.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = title
p.font.size = Pt(9)
p.font.color.rgb = PRIMARY
p.font.bold = True
for item in items:
p2 = tf.add_paragraph()
p2.text = f"{item}"
p2.font.size = Pt(7)
p2.font.color.rgb = BODY
p2.space_before = Pt(1)
return shape
def add_arrow(slide, start_left, start_top, end_left, end_top):
connector = slide.shapes.add_connector(
1, start_left, start_top, end_left, end_top) # 1 = straight
connector.line.color.rgb = PRIMARY
connector.line.width = Pt(1.5)
return connector
def add_table(slide, left, top, width, rows_data, col_widths=None):
rows = len(rows_data)
cols = len(rows_data[0])
table_shape = slide.shapes.add_table(rows, cols, left, top, width, Inches(0.3 * rows))
table = table_shape.table
if col_widths:
for i, w in enumerate(col_widths):
table.columns[i].width = w
for ri, row in enumerate(rows_data):
for ci, val in enumerate(row):
cell = table.cell(ri, ci)
cell.text = str(val)
p = cell.text_frame.paragraphs[0]
p.font.size = Pt(8)
if ri == 0:
p.font.bold = True
p.font.color.rgb = WHITE
cell.fill.solid()
cell.fill.fore_color.rgb = PRIMARY
else:
p.font.color.rgb = BODY
if ri % 2 == 0:
cell.fill.solid()
cell.fill.fore_color.rgb = LIGHT_BG
p.alignment = PP_ALIGN.CENTER if ci > 0 else PP_ALIGN.LEFT
def title_slide(prs, title, subtitle):
slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank
# 배경
bg = slide.background.fill
bg.solid()
bg.fore_color.rgb = PRIMARY
# 타이틀
add_textbox(slide, Inches(1), Inches(2), Inches(8), Inches(1),
title, font_size=32, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
add_textbox(slide, Inches(1), Inches(3.2), Inches(8), Inches(0.8),
subtitle, font_size=16, color=RGBColor(180, 200, 230), align=PP_ALIGN.CENTER)
add_textbox(slide, Inches(1), Inches(5.5), Inches(8), Inches(0.5),
"아큐라시스템 | 2026년 3월", font_size=12, color=RGBColor(150, 170, 200), align=PP_ALIGN.CENTER)
return slide
def section_slide(prs, num, title, subtitle=""):
slide = prs.slides.add_slide(prs.slide_layouts[6])
bg = slide.background.fill
bg.solid()
bg.fore_color.rgb = RGBColor(25, 50, 100)
# 큰 번호
add_textbox(slide, Inches(0.8), Inches(1.5), Inches(2), Inches(1.2),
f"{num:02d}", font_size=72, color=ACCENT, bold=True)
# 구분선
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(0.8), Inches(3.0), Inches(8), Pt(3))
shape.fill.solid()
shape.fill.fore_color.rgb = ACCENT
shape.line.fill.background()
# 타이틀
add_textbox(slide, Inches(0.8), Inches(3.3), Inches(9), Inches(0.8),
title, font_size=28, color=WHITE, bold=True)
# 서브타이틀
if subtitle:
add_textbox(slide, Inches(0.8), Inches(4.3), Inches(8), Inches(0.5),
subtitle, font_size=12, color=RGBColor(150, 180, 220))
return slide
def content_slide(prs, title):
slide = prs.slides.add_slide(prs.slide_layouts[6])
# 상단 바
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, Inches(10), Inches(0.6))
shape.fill.solid()
shape.fill.fore_color.rgb = PRIMARY
shape.line.fill.background()
add_textbox(slide, Inches(0.3), Inches(0.05), Inches(9), Inches(0.5),
title, font_size=16, color=WHITE, bold=True)
return slide
def build():
prs = Presentation()
prs.slide_width = Inches(10)
prs.slide_height = Inches(7.5)
# ============================================================
# 표지
# ============================================================
title_slide(prs,
"한화오션 EPU\nWork Time Manager (WTM)",
"시스템 설계서 — 요구사항정의 · To-Be · 시스템구성 · 기능정의 · 설계")
# ============================================================
# 목차
# ============================================================
slide = content_slide(prs, "목차")
toc = [
"01. 프로젝트 개요",
"02. 요구사항 정의서",
"03. As-Is / To-Be 분석",
"04. 시스템 구성도",
"05. 기능 정의서",
"06. 데이터베이스 설계",
"07. API 설계",
"08. 화면 구성",
"09. 인터페이스 설계 (SAP/P6/Cognite)",
"10. 보안 설계",
"11. 단계별 추진 일정",
]
y = 1.0
for item in toc:
add_textbox(slide, Inches(1.5), Inches(y), Inches(7), Inches(0.35),
item, font_size=14, color=DARK)
y += 0.4
# ============================================================
# 01. 프로젝트 개요
# ============================================================
section_slide(prs, 1, "프로젝트 개요", "WTM 시스템 범위, 기술 스택, 추진 구조")
slide = content_slide(prs, "01. 프로젝트 개요")
overview_data = [
["항목", "내용"],
["프로젝트명", "WTM (Work Time Manager)"],
["고객", "한화오션 EPU (Engineering Procurement Unit)"],
["목적", "EPC 프로젝트 인력 시수 관리, WBS 연동, 결재, 리포트"],
["기능 요구사항", "86개 (PH1 Y=62 / PH2 이관=24)"],
["비기능 요구사항", "17개 (NF.1~17)"],
["화면 수", "49개 (PH1-1차 26 / PH1-2차 11 / PH2 12)"],
["API 수", "79개 (PH1-1: 67 / PH1-2: 6 / PH2: 6)"],
["기술 스택", "Spring Boot 3.5.0 + Java 21 (wbx-spring 프레임워크)"],
["DB", "Azure SQL (프로필 전환: Oracle/MSSQL/MySQL/PG)"],
["인증", "Azure Entra ID SSO + MFA/TOTP"],
]
add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
overview_data, col_widths=[Inches(2.5), Inches(6.5)])
# ============================================================
# 02. 요구사항 정의서
# ============================================================
section_slide(prs, 2, "요구사항 정의서", "기능 86개 (Y=62/N=24) + 비기능 17개 + 확인필요사항 7건")
# 기능 요구사항 요약
slide = content_slide(prs, "02-1. 기능 요구사항 (86개) — 영역별 분류")
req_data = [
["영역", "PH1 (Y)", "PH2 이관 (N)", "합계"],
["User Registration (No.1~7)", "5", "2", "7"],
["Login / 인증 (No.8~16)", "8", "1", "9"],
["User Home / 권한 (No.17~24)", "6", "2", "8"],
["Project / WBS (No.25~41)", "14", "3", "17"],
["Resource Assignment (No.42~54)", "6", "7", "13"],
["Time Sheet (No.55~69)", "13", "2", "15"],
["Approval (No.70~75)", "6", "0", "6"],
["Reporting (No.76~86)", "2", "9", "11"],
["합계", "62", "24", "86"],
]
add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
req_data, col_widths=[Inches(4), Inches(2), Inches(2), Inches(1)])
# 비기능 요구사항
slide = content_slide(prs, "02-2. 비기능 요구사항 (17개)")
nf_data = [
["No.", "카테고리", "요구사항", "비고"],
["NF.1~2", "클라우드", "Azure Hybrid Security Zone 내 구성", "MS Azure"],
["NF.3", "보안(서버)", "HIWARE, V3, Secuver TOS", "한화 표준"],
["NF.4", "보안(DB)", "Cubeone(암호화), Dbsafer(접근제어)", "한화 표준"],
["NF.5", "보안(Cloud)", "Defender + Analytics", "Native"],
["NF.6~7", "정보보호", "보안성 심의, 컴플라이언스", "그룹 표준"],
["NF.8~10", "모니터링", "onTune(SMS), Maxguage(DB), Jennifer(WAS)", "한화 표준"],
["NF.11", "인증(내부)", "Azure Entra ID SSO", ""],
["NF.12", "인증(외부)", "2-Way 인증 (MFA)", "TOTP"],
["NF.13", "I/F(HR)", "SAP BTP 경유 SuccessFactors 연동", ""],
["NF.14", "I/F(P6)", "파일 기반 (물리적 I/F 없음)", ""],
["NF.15", "I/F(Cognite)", "Extractor 서버 구성", "PH2"],
["NF.16~17", "아키텍처", "이중화, 백업 전략", ""],
]
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
nf_data, col_widths=[Inches(1), Inches(1.5), Inches(4.5), Inches(1.5)])
# 확인필요사항
slide = content_slide(prs, "02-3. 확인필요사항 (7건)")
confirm_data = [
["#", "영역", "내용", "현재 상태"],
["1", "User", "파트너사/외주 인력 관리 정책", "파트너 마스터 미연계"],
["2", "권한", "사용자 관리규정 및 권한정책", "No.18 확정 (기능 기반 역할)"],
["3", "WBS", "WBS별 Resource Plan 방식", "TT에서 투입시수 세팅"],
["4", "배정", "인력 배치 시 스케줄 확인 절차", "Discipline별 인력 선정 후 제출"],
["5", "EPC", "C단계 시수 입력 방안", "C단계 대상 제외 (EPU만)"],
["6", "결재", "업무별 승인 절차", "User→DL→PM 3단계 확정"],
["7", "배정", "Project Assignment 프로세스", "SA 생성→PM 배정→확정"],
]
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
confirm_data, col_widths=[Inches(0.5), Inches(1), Inches(3.5), Inches(3.5)])
# ============================================================
# 03. As-Is / To-Be
# ============================================================
section_slide(prs, 3, "As-Is / To-Be 분석", "Excel 수작업 → WTM 시스템 전환 효과")
slide = content_slide(prs, "03. As-Is → To-Be 전환")
# As-Is 박스들
add_textbox(slide, Inches(0.5), Inches(0.9), Inches(4), Inches(0.4),
"As-Is (현재)", font_size=14, color=RED_ACCENT, bold=True)
as_is = [
("Excel 기반 시수 관리", "수작업 입력, 오류 빈번"),
("이메일 기반 결재", "추적 불가, 지연"),
("P6 WBS 수동 매핑", "버전 관리 어려움"),
("리포트 수동 집계", "실시간 분석 불가"),
("인력 배정 구두 전달", "이력 관리 없음"),
]
y = 1.4
for title, desc in as_is:
add_multiline_box(slide, Inches(0.5), Inches(y), Inches(4), Inches(0.55),
title, [desc], fill_color=RGBColor(255, 235, 235))
y += 0.6
# 화살표 영역
add_textbox(slide, Inches(4.5), Inches(2.5), Inches(1), Inches(0.5),
"", font_size=36, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
# To-Be 박스들
add_textbox(slide, Inches(5.5), Inches(0.9), Inches(4), Inches(0.4),
"To-Be (WTM 시스템)", font_size=14, color=ACCENT, bold=True)
to_be = [
("통합 시수 입력 (3종)", "Non-Project/Other/EPC 탭 전환, 규칙 엔진"),
("온라인 결재 워크플로우", "User→DL→PM 3단계, 일괄 승인, 실시간 알림"),
("P6 WBS 자동 파싱", "Canonical WBS 매핑, 버전 관리, 비교 UI"),
("실시간 리포트", "프로젝트별/WBS별 시수 분석, Excel Export"),
("체계적 인력 배정", "프로젝트-Discipline-인력 매핑, 이력 관리"),
]
y = 1.4
for title, desc in to_be:
add_multiline_box(slide, Inches(5.5), Inches(y), Inches(4), Inches(0.55),
title, [desc], fill_color=RGBColor(230, 245, 255))
y += 0.6
# 하단 기대효과
add_textbox(slide, Inches(0.5), Inches(5.0), Inches(9), Inches(0.3),
"기대효과", font_size=12, color=PRIMARY, bold=True)
effects = "• 시수 입력 오류 90% 감소 • 결재 처리 시간 70% 단축 • 실시간 프로젝트 시수 현황 파악 • WBS 변경 추적 자동화"
add_textbox(slide, Inches(0.5), Inches(5.3), Inches(9), Inches(0.5),
effects, font_size=10, color=BODY)
# ============================================================
# 04. 시스템 구성도
# ============================================================
section_slide(prs, 4, "시스템 구성도", "전체 아키텍처, 기술 스택, 네트워크 구성")
# 전체 아키텍처
slide = content_slide(prs, "04-1. 전체 시스템 아키텍처")
# 클라이언트 영역
add_textbox(slide, Inches(0.3), Inches(0.9), Inches(2), Inches(0.3),
"클라이언트", font_size=10, color=BODY, bold=True)
clients = ["WBX No-Code", "Web Browser", "Mobile"]
cx = 0.3
for c in clients:
add_box(slide, Inches(cx), Inches(1.2), Inches(1.4), Inches(0.5),
c, fill_color=RGBColor(200, 220, 255), font_size=8)
cx += 1.6
# Nginx
add_box(slide, Inches(5.5), Inches(1.2), Inches(1.8), Inches(0.5),
"Nginx (SSL/LB)", fill_color=RGBColor(220, 220, 220), font_color=DARK, font_size=9)
# Spring Boot WTM
add_multiline_box(slide, Inches(0.3), Inches(2.2), Inches(4.5), Inches(2.8),
"Spring Boot WTM-API (wbx-spring 프레임워크)", [
"JWT 인증 / Azure Entra ID SSO / MFA",
"RBAC 권한 (dept_scope)",
"통합 결재 엔진 (Handler Registry)",
"SSE 실시간 알림",
"시수 입력 3종 + 규칙 엔진",
"WBS/TEAL 관리 + P6 파서",
"리포트 (QueryDSL)",
"SAP BTP HR 연동",
], fill_color=RGBColor(230, 240, 255))
# WBX FastAPI
add_multiline_box(slide, Inches(5.2), Inches(2.2), Inches(2.3), Inches(2.0),
"WBX FastAPI (선택)", [
"문서관리",
"이메일",
"게시판/일정",
"/api/gw/*"
], fill_color=RGBColor(245, 240, 255))
# DB
add_multiline_box(slide, Inches(7.8), Inches(2.2), Inches(2), Inches(1.5),
"Database", [
"wtm_db (Azure SQL)",
"wbx_gw (MySQL)",
"프로필 전환 지원"
], fill_color=RGBColor(255, 245, 230))
# Redis
add_box(slide, Inches(7.8), Inches(4.0), Inches(1), Inches(0.5),
"Redis", fill_color=GREEN_BG, font_size=9)
# Blob Storage
add_box(slide, Inches(8.9), Inches(4.0), Inches(1), Inches(0.5),
"Storage", fill_color=ORANGE_BG, font_size=9)
# SSO
add_box(slide, Inches(5.2), Inches(4.5), Inches(2.3), Inches(0.5),
"Azure Entra ID (SSO+MFA)", fill_color=RGBColor(255, 235, 235), font_size=8)
# SAP BTP
add_box(slide, Inches(0.3), Inches(5.3), Inches(2), Inches(0.5),
"SAP BTP (HR)", fill_color=RGBColor(255, 240, 220), font_size=9)
# P6
add_box(slide, Inches(2.5), Inches(5.3), Inches(1.5), Inches(0.5),
"P6 (WBS)", fill_color=RGBColor(255, 240, 220), font_size=9)
# Cognite
add_box(slide, Inches(4.2), Inches(5.3), Inches(1.5), Inches(0.5),
"Cognite", fill_color=RGBColor(255, 240, 220), font_size=9)
# 기술 스택 슬라이드
slide = content_slide(prs, "04-2. 기술 스택")
tech_data = [
["레이어", "기술", "버전/비고"],
["Language", "Java", "21 (LTS)"],
["Framework", "Spring Boot + wbx-spring", "3.5.0"],
["Security", "Spring Security 6 + OAuth2", "Entra ID SSO + MFA"],
["ORM", "Spring Data JPA + QueryDSL", "Hibernate 6"],
["DB", "Azure SQL / Oracle / MySQL / PG", "프로필 전환"],
["Migration", "Flyway", "DBMS별 DDL 분리"],
["Cache", "Redis", "세션 + 캐시"],
["WEB/WAS", "Nginx + Embedded Tomcat", "이중화"],
["CI/CD", "GitHub Actions + Azure DevOps", "승인 게이트"],
["형상관리", "GitHub + Azure Repos", "SemVer"],
["모니터링", "Jennifer + Actuator + Prometheus", "한화 표준"],
["Cloud", "Microsoft Azure", "Hybrid Security Zone"],
]
add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
tech_data, col_widths=[Inches(2), Inches(3.5), Inches(3.5)])
# ============================================================
# 05. 기능 정의서
# ============================================================
section_slide(prs, 5, "기능 정의서", "시수 입력 3종, WBS/TEAL, 결재 워크플로우, 리포트")
# 시수 입력
slide = content_slide(prs, "05-1. 시수 입력 (3종 통합 UI)")
# 탭 UI 도식
tabs = ["Non-Project", "Other Project", "EPC Project"]
tx = 0.5
for i, tab in enumerate(tabs):
color = ACCENT if i == 2 else RGBColor(180, 180, 180)
add_box(slide, Inches(tx), Inches(1.0), Inches(2), Inches(0.4),
tab, fill_color=RGBColor(230, 240, 255) if i == 2 else RGBColor(245, 245, 245),
font_color=color, font_size=9)
tx += 2.2
# EPC 입력 폼 도식
fields = [
"프로젝트: [EPU-2025-001 ▼]",
"WBS: [E.01.03 Piping Detail ▼]",
"TEAL: [Detail Engineering ▼]",
]
fy = 1.6
for field in fields:
add_textbox(slide, Inches(0.8), Inches(fy), Inches(6), Inches(0.3),
field, font_size=10, color=DARK)
fy += 0.35
# 주간 그리드
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "합계"]
vals = ["8.0", "8.0", "8.0", "8.0", "4.0", "-", "36.0h"]
grid_data = [days, vals]
add_table(slide, Inches(0.5), Inches(2.9), Inches(6.5),
grid_data, col_widths=[Inches(0.9)] * 7)
# 규칙 엔진 박스
add_multiline_box(slide, Inches(7.3), Inches(1.0), Inches(2.5), Inches(2.5),
"규칙 엔진", [
"일 최소 8시간",
"주 최대 52시간",
"Activity 최소 1개",
"Location별 규칙",
"미래 날짜 입력 불가",
"초과 시 경고/차단",
], fill_color=ORANGE_BG)
# 하단 설명
add_textbox(slide, Inches(0.5), Inches(3.6), Inches(6), Inches(0.3),
"[임시 저장] [결재 요청] 주간 합계: 36.0 / 52h",
font_size=9, color=BODY)
ts_desc = [
("Non-Project (No.55~59)", "Overhead Type 선택 → TEAL Activity → 시간 입력. WBS/프로젝트 입력 불가"),
("Other Project (No.60~62)", "프로젝트 선택 → Canonical WBS L2~4 → Activity → 시간. P6 연동 불필요"),
("EPC Project (No.64~69)", "프로젝트 → Canonical WBS L2~5 → TEAL → 시간. Revision 관리(PH1-2)"),
]
fy = 4.2
for title, desc in ts_desc:
add_textbox(slide, Inches(0.5), Inches(fy), Inches(9), Inches(0.2),
title, font_size=9, color=PRIMARY, bold=True)
add_textbox(slide, Inches(0.5), Inches(fy + 0.2), Inches(9), Inches(0.25),
desc, font_size=8, color=BODY)
fy += 0.5
# WBS/TEAL
slide = content_slide(prs, "05-2. WBS · TEAL 관리")
# Canonical WBS 구조
add_textbox(slide, Inches(0.5), Inches(0.9), Inches(4), Inches(0.3),
"Canonical WBS 구조 (No.34)", font_size=12, color=PRIMARY, bold=True)
wbs_levels = [
["Level", "구분", "설명", "예시"],
["L1", "Project", "프로젝트", "EPU-2025-001"],
["L2", "Phase", "Engineering/Procurement/Construction/...", "Engineering"],
["L3", "Asset/Area", "자산 또는 영역", "Hull Structure"],
["L4", "Work/Discipline", "작업 또는 Discipline", "Piping"],
["L5", "Deliverable", "산출물 (Eng/SCM only)", "Detail Drawing"],
]
add_table(slide, Inches(0.5), Inches(1.3), Inches(9),
wbs_levels, col_widths=[Inches(0.8), Inches(1.5), Inches(4), Inches(2.5)])
# WBS 흐름
add_textbox(slide, Inches(0.5), Inches(3.5), Inches(4), Inches(0.3),
"WBS 업로드 흐름", font_size=12, color=PRIMARY, bold=True)
flow_steps = ["P6 Export\n(PM 업로드)", "WBS 파싱\n(Level 1~5)", "Canonical\nWBS 매핑", "PCM 승인\n버전 등록", "TEAL\nActivity 연결"]
fx = 0.3
for i, step in enumerate(flow_steps):
add_box(slide, Inches(fx), Inches(3.9), Inches(1.6), Inches(0.8),
step, fill_color=RGBColor(230, 240, 255) if i % 2 == 0 else GREEN_BG,
font_size=8, font_color=DARK)
if i < len(flow_steps) - 1:
add_textbox(slide, Inches(fx + 1.6), Inches(4.05), Inches(0.3), Inches(0.3),
"", font_size=16, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
fx += 1.9
# 결재 워크플로우
slide = content_slide(prs, "05-3. 결재 워크플로우")
# 결재 흐름도
steps = [
("User\n시수 입력", RGBColor(200, 220, 255)),
("제출\n(결재 요청)", RGBColor(255, 240, 200)),
("DL\n1차 결재", RGBColor(220, 240, 220)),
("PM\n최종 결재", RGBColor(220, 240, 220)),
("승인 완료\n(APPROVED)", RGBColor(200, 255, 200)),
]
sx = 0.3
for i, (label, color) in enumerate(steps):
add_box(slide, Inches(sx), Inches(1.3), Inches(1.6), Inches(0.9),
label, fill_color=color, font_color=DARK, font_size=10)
if i < len(steps) - 1:
add_textbox(slide, Inches(sx + 1.6), Inches(1.5), Inches(0.3), Inches(0.3),
"", font_size=18, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
sx += 1.9
# 반려 흐름
add_box(slide, Inches(3.5), Inches(2.5), Inches(2.5), Inches(0.5),
"반려 → User 수정 후 재제출", fill_color=RGBColor(255, 230, 230),
font_color=RED_ACCENT, font_size=9)
# 결재 기능 표
approval_data = [
["기능", "설명", "요구사항"],
["시수 결재 요청", "User가 주간 시수 작성 후 제출", "No.70"],
["승인/반려 + 코멘트", "DL/PM이 승인 또는 반려 (사유 필수)", "No.72"],
["일괄 승인", "여러 시수를 한번에 승인", "No.73"],
["결재 이력 조회", "과거 결재 내역 전체 조회", "No.74"],
["초과 하이라이트", "기준 초과 시 결재자에게 강조 표시", "No.75 (PH1-2)"],
["미완료 알림", "미입력/미제출 시수 자동 리마인더", "No.70"],
]
add_table(slide, Inches(0.3), Inches(3.3), Inches(9.4),
approval_data, col_widths=[Inches(2), Inches(5), Inches(1.5)])
# 리포트
slide = content_slide(prs, "05-4. 리포트")
report_data = [
["리포트", "설명", "Phase", "필터"],
["프로젝트별 시수 분석", "프로젝트별 실제 투입 Manhour", "PH1-1", "기간, 프로젝트, Discipline"],
["WBS Level별 시수 분석", "Canonical WBS Level별 Manhour", "PH1-1", "기간, 프로젝트, WBS Level"],
["Phase별 시수 비율", "Engineering/Procurement 등 비율", "PH1-2", "기간, 프로젝트"],
["Non-Project 비율", "Overhead Manhour 투입 비율", "PH1-2", "기간, 부서"],
["WBS 버전 이력", "WBS 변경 이력 조회", "PH1-2", "프로젝트, 버전"],
["Discipline 생산성", "Discipline별 생산성 분석", "PH2", "기간, Discipline"],
]
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
report_data, col_widths=[Inches(2.2), Inches(3.5), Inches(1), Inches(2)])
# ============================================================
# 06. 데이터베이스 설계
# ============================================================
section_slide(prs, 6, "데이터베이스 설계", "ERD, 주요 테이블 14개, Flyway 마이그레이션")
slide = content_slide(prs, "06-1. ERD 개요 (wtm_db)")
# ERD 도식
entities = [
("users", Inches(0.3), Inches(1.0), ["id, employee_number", "email, full_name", "department, discipline", "is_active, mfa_enabled"]),
("projects", Inches(3.3), Inches(1.0), ["id, project_code", "name, project_type", "status, pm_user_id"]),
("canonical_wbs", Inches(6.3), Inches(1.0), ["id, project_id", "wbs_code, level", "name, discipline"]),
("timesheets", Inches(0.3), Inches(3.2), ["id, user_id", "week_start_date", "status, total_hours"]),
("timesheet_entries", Inches(3.3), Inches(3.2), ["id, timesheet_id", "entry_type, entry_date", "hours, epc_project_id", "canonical_wbs_id"]),
("tt_approvals", Inches(6.3), Inches(3.2), ["id, timesheet_id", "requester_id, status", "submitted_at"]),
("tt_approval_lines", Inches(6.3), Inches(5.0), ["id, approval_id", "approver_id, order", "role_code, status"]),
("wbs_versions", Inches(3.3), Inches(5.0), ["id, project_id", "version_number", "effective_date"]),
]
for name, x, y, fields in entities:
h = Inches(0.2 + len(fields) * 0.2)
add_multiline_box(slide, x, y, Inches(2.7), h, name, fields, fill_color=LIGHT_BG)
# DB 테이블 목록
slide = content_slide(prs, "06-2. 주요 테이블 목록")
db_data = [
["테이블", "용도", "주요 컬럼"],
["users", "사용자 (HR 필드 포함)", "employee_number, business_unit~part (5레벨)"],
["projects", "프로젝트 (EPC/Other/Non)", "project_code, type, status, pm_user_id"],
["wbs_versions", "P6 WBS 스냅샷", "version_number, effective_date, source_filename"],
["wbs_nodes", "WBS 트리 (L1~L5)", "wbs_code, level, parent_id, planned_hours"],
["canonical_wbs", "정규 WBS", "wbs_code, mapped_p6_code, discipline"],
["teal_entries", "TEAL Activity", "canonical_wbs_id, activity_code, activity_name"],
["timesheets", "주간 시수 헤더", "user_id, week_start_date, status, total_hours"],
["timesheet_entries", "일별 시수 상세", "entry_type, entry_date, hours, remark"],
["tt_approvals", "결재 요청", "timesheet_id, requester_id, status"],
["tt_approval_lines", "결재 라인 (DL/PM)", "approver_id, approval_order, role_code"],
["work_rules", "근무 규칙", "location_code, min_daily, max_weekly"],
["overhead_types", "Overhead 유형", "code, name (SA 관리)"],
["project_assignments", "인력 배정", "project_id, user_id, discipline"],
]
add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
db_data, col_widths=[Inches(2), Inches(2.5), Inches(4.5)])
# ============================================================
# 07. API 설계
# ============================================================
section_slide(prs, 7, "API 설계", "REST API 79개 — /api/wtm/ prefix")
slide = content_slide(prs, "07. REST API 스펙 (79개, /api/wtm/)")
api_data = [
["모듈", "PH1-1", "PH1-2", "PH2", "합계", "주요 Endpoint"],
["Auth", "8", "0", "0", "8", "/auth/login, /auth/sso, /auth/mfa/*"],
["Users", "7", "1", "0", "8", "/users, /users/upload/internal"],
["Projects", "7", "0", "0", "7", "/projects, /projects/my, /projects/{id}/members"],
["WBS", "6", "1", "0", "7", "/projects/{id}/wbs/upload, /canonical-wbs"],
["TEAL", "4", "0", "0", "4", "/projects/{id}/teal/active"],
["Timesheets", "8", "0", "0", "8", "/timesheets/week, /entries/batch, /upload"],
["Approvals", "7", "1", "0", "8", "/approvals/unified/action/{type}/{id}/*"],
["Reports", "4", "2", "3", "9", "/reports/project-hours, /wbs-hours"],
["Resource", "8", "0", "0", "8", "/projects/{id}/assignments"],
["Config", "4", "0", "0", "4", "/overhead-types, /work-rules"],
["Integration", "1", "1", "1", "3", "/integration/hr/sync, /cognite/export"],
["Notification", "3", "0", "0", "3", "/notifications/stream (SSE)"],
["합계", "67", "6", "6", "79", ""],
]
add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
api_data, col_widths=[Inches(1.2), Inches(0.7), Inches(0.7), Inches(0.7), Inches(0.7), Inches(5)])
# ============================================================
# 08. 화면 구성
# ============================================================
section_slide(prs, 8, "화면 구성", "전체 49개 화면 — PH1-1차 26 / PH1-2차 11 / PH2 12")
slide = content_slide(prs, "08. 전체 화면 목록 (49개)")
screen_data = [
["그룹", "PH1-1차 (26개)", "PH1-2차 (11개)", "PH2 (12개)"],
["로그인/인증", "로그인, SSO, PW찾기, PW만료 (4)", "", ""],
["홈/권한", "역할별 홈 5종 (5)", "권한라우팅, SA로그 (2)", ""],
["사용자/인력", "내부/외주 업로드, 관리 (3)", "", "외주 개별입력 (1)"],
["프로젝트/WBS", "등록, P6, WBS, TEAL 등 (6)", "WBS비교, 시수조회 (3)", ""],
["시수 입력", "Non/Other/EPC 입력, Excel (4)", "EPC Revision (1)", "벤치마킹, Rate (2)"],
["결재", "결재요청, 목록, 일괄승인 (3)", "초과알림, MFA (2)", ""],
["리포트", "프로젝트별, WBS별 (2)", "Phase비율, NP비율 (2)", "RCP 3종, Discipline 등 (5)"],
["기타", "", "HR배치 (1)", "외주포털, Favorite 등 (4)"],
]
add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
screen_data, col_widths=[Inches(1.5), Inches(3), Inches(2.5), Inches(2.5)])
# ============================================================
# 09. 인터페이스 설계
# ============================================================
section_slide(prs, 9, "인터페이스 설계", "SAP BTP (HR), P6 (WBS 파일), Cognite (Export)")
slide = content_slide(prs, "09. 외부 시스템 인터페이스")
# SAP BTP
add_multiline_box(slide, Inches(0.3), Inches(1.0), Inches(2.5), Inches(2),
"SAP SuccessFactors", [
"HR Master Data",
"Employee Central",
"OData API"
], fill_color=RGBColor(255, 245, 220))
add_textbox(slide, Inches(2.8), Inches(1.7), Inches(0.8), Inches(0.3),
"→ BTP →", font_size=10, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
add_multiline_box(slide, Inches(3.6), Inches(1.0), Inches(2.5), Inches(2),
"SAP BTP (CPI)", [
"OData → REST 변환",
"필드 매핑",
"일 1회 배치 (PH1-2)",
"실시간 이벤트 (PH2)"
], fill_color=RGBColor(240, 235, 255))
add_textbox(slide, Inches(6.1), Inches(1.7), Inches(0.5), Inches(0.3),
"", font_size=14, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
add_multiline_box(slide, Inches(6.6), Inches(1.0), Inches(3), Inches(2),
"WTM Spring Boot", [
"POST /api/wtm/integration/hr/sync",
"HrSyncService",
"사용자 자동 동기화",
"PH1-1: Excel 수동 업로드"
], fill_color=RGBColor(230, 240, 255))
# P6
add_box(slide, Inches(0.3), Inches(3.5), Inches(2.5), Inches(0.7),
"P6 (WBS Export)\n파일 기반 (No I/F)", fill_color=RGBColor(255, 245, 220),
font_color=DARK, font_size=9)
add_textbox(slide, Inches(2.8), Inches(3.65), Inches(1), Inches(0.3),
"→ 파일 →", font_size=9, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
add_box(slide, Inches(3.8), Inches(3.5), Inches(3), Inches(0.7),
"WTM: P6 WBS 파서\nPM 업로드 → PCM 승인", fill_color=RGBColor(230, 240, 255),
font_color=DARK, font_size=9)
# Cognite
add_box(slide, Inches(0.3), Inches(4.5), Inches(2.5), Inches(0.7),
"Cognite (PH2)\nExtractor 서버", fill_color=RGBColor(255, 245, 220),
font_color=DARK, font_size=9)
add_textbox(slide, Inches(2.8), Inches(4.65), Inches(1), Inches(0.3),
"← Export", font_size=9, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
add_box(slide, Inches(3.8), Inches(4.5), Inches(5.5), Inches(0.7),
"Export: Employee + Project + WBS + Time Fact + Mapping Version Metadata",
fill_color=RGBColor(230, 240, 255), font_color=DARK, font_size=8)
# ============================================================
# 10. 보안 설계
# ============================================================
section_slide(prs, 10, "보안 설계", "한화오션 표준 보안 SW 12종, Azure Entra ID SSO + MFA")
slide = content_slide(prs, "10. 한화오션 표준 보안 SW")
sec_data = [
["분류", "SW", "용도", "구성"],
["서버 보안", "HIWARE", "서버 접근 제어", "Agent 설치"],
["서버 보안", "V3 (AhnLab)", "서버 백신", "Agent 설치"],
["서버 보안", "Secuver TOS", "파일 무결성", "Agent 설치"],
["DB 보안", "Cubeone", "DB 암호화 (컬럼)", "연동"],
["DB 보안", "Dbsafer", "DB 접근 제어/감사", "Proxy"],
["클라우드", "Azure Defender", "위협 탐지", "Native"],
["모니터링", "onTune", "SMS (서버)", "Agent"],
["모니터링", "Maxguage", "DB 모니터링", "Proxy"],
["모니터링", "Jennifer", "WAS APM", "-javaagent"],
["인증", "Azure Entra ID", "SSO + Conditional Access", "OAuth2 OIDC"],
["인증", "TOTP (MFA)", "외부사용자 2-Way 인증", "Google/MS Auth"],
]
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
sec_data, col_widths=[Inches(1.3), Inches(2), Inches(3), Inches(2.5)])
# ============================================================
# 11. 단계별 추진 일정
# ============================================================
section_slide(prs, 11, "단계별 추진 일정", "PH1-1차(9주) + PH1-2차(4주) = 15 M/M")
slide = content_slide(prs, "11-1. 전체 일정 (Gantt)")
# Gantt-like 도식
months = ["4월", "5월", "6월", "7월~"]
mx = 2.5
for m in months:
add_box(slide, Inches(mx), Inches(1.0), Inches(1.8), Inches(0.35),
m, fill_color=PRIMARY, font_color=WHITE, font_size=10)
mx += 1.8
phases = [
("분석·설계", 2.5, 2.5, RGBColor(100, 160, 230)),
("BE 개발", 3.0, 5.5, RGBColor(70, 140, 200)),
("FE 개발", 3.5, 5.0, RGBColor(90, 170, 220)),
("QA·UAT", 6.5, 2.0, RGBColor(230, 160, 60)),
("PH1-2차", 6.1, 3.6, RGBColor(140, 200, 140)),
]
gy = 1.6
for name, start, width, color in phases:
add_box(slide, Inches(start), Inches(gy), Inches(width), Inches(0.35),
name, fill_color=color, font_color=WHITE, font_size=9)
gy += 0.45
# 마일스톤
milestones = [
("4/10", "설계 확정"),
("5/7", "BE API 완료"),
("5/20", "FE 완료"),
("5/31", "★ PH1-1 오픈"),
("6/30", "★ PH1-2 오픈"),
]
add_textbox(slide, Inches(0.5), Inches(4.0), Inches(9), Inches(0.3),
"주요 마일스톤", font_size=12, color=PRIMARY, bold=True)
my = 4.4
for date, desc in milestones:
add_textbox(slide, Inches(0.5), Inches(my), Inches(1.5), Inches(0.25),
date, font_size=10, color=ACCENT, bold=True)
add_textbox(slide, Inches(2.0), Inches(my), Inches(7), Inches(0.25),
desc, font_size=10, color=DARK)
my += 0.3
# 인력 투입
slide = content_slide(prs, "11-2. 인력 투입 계획")
manpower = [
["역할", "PH1-1차 (9주)", "PH1-2차 (4주)", "담당"],
["BE 시니어 / 기술리드", "2.0 M/M", "1.0 M/M", "아키텍처, SSO, 결재, 코드리뷰"],
["풀스택 엔지니어 ①", "2.0 M/M", "1.0 M/M", "WBS, TEAL, 프로젝트"],
["풀스택 엔지니어 ②", "2.0 M/M", "1.0 M/M", "시수 입력 3종, 규칙 엔진"],
["풀스택 엔지니어 ③", "2.0 M/M", "1.0 M/M", "리포트, 로그인, 사용자"],
["DevOps (파트타임)", "1.0 M/M", "0.5 M/M", "Azure 인프라, CI/CD"],
["QA 겸임", "1.0 M/M", "0.5 M/M", "통합테스트, UAT 지원"],
["합계", "~10.0 M/M", "~5.0 M/M", "총 PH1: ~15 M/M"],
]
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
manpower, col_widths=[Inches(2.2), Inches(1.5), Inches(1.5), Inches(4)])
# ============================================================
# 마지막
# ============================================================
slide = prs.slides.add_slide(prs.slide_layouts[6])
bg = slide.background.fill
bg.solid()
bg.fore_color.rgb = PRIMARY
add_textbox(slide, Inches(1), Inches(2.5), Inches(8), Inches(1),
"감사합니다", font_size=36, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
add_textbox(slide, Inches(1), Inches(3.8), Inches(8), Inches(0.5),
"한화오션 EPU Time Tracking 시스템을\n함께 성공적으로 완수하겠습니다.",
font_size=14, color=RGBColor(180, 200, 230), align=PP_ALIGN.CENTER)
add_textbox(slide, Inches(1), Inches(5), Inches(8), Inches(0.5),
"아큐라시스템 | accura@accurasoft.co.kr",
font_size=12, color=RGBColor(150, 170, 200), align=PP_ALIGN.CENTER)
prs.save(OUTPUT)
size_kb = os.path.getsize(OUTPUT) // 1024
try:
print(f"Generated: {OUTPUT} ({size_kb} KB)")
except UnicodeEncodeError:
print(f"Generated: PPT ({size_kb} KB)")
if __name__ == "__main__":
build()

파일 보기

@@ -0,0 +1,289 @@
================================================================================
WBX Spring Framework - 개발환경 사전 설치 가이드
Developer Quick Setup Guide v1.0
아큐라시스템 | 2026년 3월
================================================================================
이 문서는 WBX Spring Framework 개발을 시작하기 전에 필요한
소프트웨어 설치 및 환경 설정을 안내합니다.
TIP: scripts\install.bat (Windows) 또는 scripts/install.sh (Linux/macOS)를
실행하면 JDK 자동 설치, 빌드 검증, .env 생성까지 한번에 처리됩니다.
--------------------------------------------------------------------------------
1. 필수 소프트웨어
--------------------------------------------------------------------------------
[1-1] JDK 21 (Eclipse Temurin LTS 권장)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
용도: Spring Boot 3.5 런타임
* Windows
winget install --id EclipseAdoptium.Temurin.21.JDK
* macOS
brew install --cask temurin@21
* Ubuntu/Debian
sudo apt update && sudo apt install -y temurin-21-jdk
(Adoptium 저장소 미등록 시 https://adoptium.net 참고)
* RHEL/Rocky
sudo yum install -y temurin-21-jdk
설치 확인:
java -version
→ openjdk version "21.x.x" 이 출력되면 정상
[1-2] Git
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
용도: 소스 코드 버전 관리, Gitea 저장소 연동
* Windows
winget install --id Git.Git
* macOS
brew install git
* Linux
sudo apt install -y git (Ubuntu)
sudo yum install -y git (RHEL)
설치 확인:
git --version
초기 설정:
git config --global user.name "홍길동"
git config --global user.email "hong@accurasoft.co.kr"
[1-3] IDE (택 1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+------------------+----------------+------------------------------------+
| IDE | 권장 대상 | 핵심 장점 |
+------------------+----------------+------------------------------------+
| IntelliJ IDEA | 메인 개발 | Spring Boot 최적, 리팩터링, DB |
| VS Code | 경량/FE 병행 | Extension Pack, DevContainer |
| Eclipse/STS | 무료/레거시 | Spring Tool Suite 플러그인 |
+------------------+----------------+------------------------------------+
IntelliJ 필수 플러그인:
- Spring Boot, Lombok, JPA Buddy, GitToolBox, .env, SonarLint
- Settings > Build > Compiler > Annotation Processor 활성화
VS Code 필수 Extension:
- Java Pack, Spring Boot Pack, Lombok, Gradle, REST Client, GitLens
--------------------------------------------------------------------------------
2. 선택 소프트웨어 (권장)
--------------------------------------------------------------------------------
[2-1] Docker Desktop
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
용도: 로컬 개발용 DB(MySQL/PostgreSQL) + Redis를 컨테이너로 실행
* Windows
winget install --id Docker.DockerDesktop
(설치 후 재부팅 필요)
* macOS
brew install --cask docker
설치 확인:
docker --version
docker compose version
주의: Docker Desktop은 대규모 기업(250명+/매출 $10M+) 유료 라이선스입니다.
대안으로 Rancher Desktop, Podman Desktop 사용 가능합니다.
[2-2] DB 직접 설치 (Docker 미사용 시)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
아래 중 프로젝트에 맞는 DBMS 1개를 선택하여 설치합니다.
+------------------+----------+------------------------------------------+
| DBMS | 버전 | 설치 |
+------------------+----------+------------------------------------------+
| MySQL | 8.0+ | winget install Oracle.MySQL |
| PostgreSQL | 14+ | winget install PostgreSQL.PostgreSQL |
| Oracle | 19c+ | 고객 DBA 설치/관리 |
| MSSQL | 2019+ | winget install Microsoft.SQLServer.2022 |
+------------------+----------+------------------------------------------+
DB 생성 예시 (MySQL):
CREATE DATABASE mos CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'jsh'@'%' IDENTIFIED BY 'jsh@';
GRANT ALL ON mos.* TO 'jsh'@'%';
[2-3] Redis
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
용도: 캐시, 세션 저장소
* Windows (Docker 권장, 네이티브 미지원)
docker run -d -p 6379:6379 redis:7-alpine
* macOS
brew install redis && brew services start redis
* Linux
sudo apt install -y redis-server (Ubuntu)
sudo yum install -y redis (RHEL)
--------------------------------------------------------------------------------
3. 빠른 시작 (Quick Start)
--------------------------------------------------------------------------------
아래 명령어를 순서대로 실행하면 개발 환경이 완성됩니다.
[방법 A] Docker 사용 (권장, 3분)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 1) 소스 클론
git clone https://git.wbx.kr/accura/wbx-spring-core.git
cd wbx-spring-core
# 2) 설치 스크립트 실행 (JDK 자동 설치 + 빌드 + .env 생성)
scripts\install.bat (Windows)
./scripts/install.sh (Linux/macOS)
# 3) Docker로 MySQL + Redis 시작
docker compose -f docker-compose-dev.yml up -d
# 4) .env 파일 수정 (DB 비밀번호를 docker-compose 설정에 맞춤)
# DB_USER=jsh
# DB_PASS=jsh@
# JWT_SECRET=dev-secret-key-for-local-development-only
# 5) 앱 실행
gradlew.bat bootRun (Windows)
./gradlew bootRun (Linux/macOS)
# 6) 확인
http://localhost:8080/actuator/health → {"status":"UP"}
http://localhost:8080/swagger-ui → API 문서
http://localhost:8080/admin/login → 관리 콘솔
[방법 B] DB 직접 설치 (Docker 없이)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 1~2) 위와 동일 (클론 + install 스크립트)
# 3) MySQL/PostgreSQL 설치 후 DB 생성
# 위 [2-2] 참고
# 4) Redis 설치
# 위 [2-3] 참고
# 5) .env 파일을 실제 DB 정보에 맞게 수정
# 6~7) 앱 실행 및 확인 (위와 동일)
--------------------------------------------------------------------------------
4. Docker Compose 서비스 구성
--------------------------------------------------------------------------------
docker-compose-dev.yml 에 포함된 서비스:
+------------------+--------+----------+------------------------------+
| 서비스 | 포트 | 계정 | 비고 |
+------------------+--------+----------+------------------------------+
| MySQL 8.0 | 3306 | jsh | PW: jsh@ (기본 프로필) |
| | | | root PW: rootpassword |
| PostgreSQL 16 | 5432 | jsh | PW: jsh@ (--profile pg) |
| Redis 7 | 6379 | - | 인증 없음 (개발용) |
+------------------+--------+----------+------------------------------+
프로필별 실행:
docker compose -f docker-compose-dev.yml up -d # MySQL + Redis
docker compose -f docker-compose-dev.yml --profile pg up -d # PostgreSQL + Redis
--------------------------------------------------------------------------------
5. .env 설정 가이드
--------------------------------------------------------------------------------
.env 파일은 install 스크립트가 자동 생성합니다.
아래 항목을 환경에 맞게 수정하세요.
[필수 수정]
JWT_SECRET 운영: 256bit 이상 랜덤 키
개발: dev-secret-key-for-local-development-only
DB_PASS 실제 DB 비밀번호
[환경별 수정]
SPRING_PROFILES_ACTIVE 프로필 조합 (아래 참고)
DB_HOST / DB_PORT DB 서버 주소
CORS_ORIGINS 프론트엔드 URL
[프로필 조합 예시]
로컬 개발 (MySQL) : local,mysql
로컬 개발 (PostgreSQL): local,postgresql
운영 (Azure + MSSQL) : prod,azure,mssql
운영 (AWS + PG) : prod,aws,postgresql
운영 (On-Premise) : prod,mysql 또는 prod,oracle
--------------------------------------------------------------------------------
6. 네트워크 포트 요약
--------------------------------------------------------------------------------
+--------+------------------+------------------------------------------+
| 포트 | 서비스 | 비고 |
+--------+------------------+------------------------------------------+
| 8080 | Spring Boot | API, Admin, Health, Swagger |
| 3306 | MySQL | 기본 DB (mysql 프로필) |
| 5432 | PostgreSQL | 대안 DB (postgresql 프로필) |
| 6379 | Redis | 캐시, 세션 |
| 8001 | WBX FastAPI | 선택, 그룹웨어 동시 운영 시 |
+--------+------------------+------------------------------------------+
--------------------------------------------------------------------------------
7. 트러블슈팅
--------------------------------------------------------------------------------
Q: install.bat 실행 시 "java 명령어 없음"
A: 스크립트가 자동으로 JDK 21을 설치합니다. 실패 시 수동 설치:
winget install --id EclipseAdoptium.Temurin.21.JDK
Q: gradlew.bat 실행 시 "not recognized"
A: 프로젝트 루트(wbx-spring-core/) 에서 실행하세요.
또는 scripts\run-install.bat 을 사용하세요.
Q: bootRun 시 DB 연결 실패
A: 1) docker compose -f docker-compose-dev.yml up -d 로 DB 시작
2) .env 파일의 DB_HOST, DB_PORT, DB_PASS 확인
3) telnet localhost 3306 으로 연결 가능 여부 확인
Q: Redis 연결 실패
A: docker ps 에서 redis 컨테이너 실행 중인지 확인
또는 Redis를 직접 설치하고 기본 포트(6379) 확인
Q: Lombok 관련 컴파일 에러
A: IDE에서 Annotation Processor 활성화 필요
IntelliJ: Settings > Build > Compiler > Annotation Processors > Enable
Q: 포트 충돌 (8080 이미 사용 중)
A: netstat -ano | findstr :8080 으로 프로세스 확인 후 종료
또는 application.yml에서 server.port 변경
--------------------------------------------------------------------------------
8. 참고 문서
--------------------------------------------------------------------------------
docs/WBX_Spring_Framework_개발자가이드.pdf 개발자 상세 가이드
docs/WBX_Spring_Framework_설치가이드_OnPremise.pdf 운영 서버 설치 가이드
docs/WBX_Spring_Framework_설치가이드_Cloud.pdf 클라우드 배포 가이드
================================================================================
문의: accura@accurasoft.co.kr
================================================================================