## Thymeleaf 사용 가이드

### 1. 기본 설정

#### 1.1 의존성 추가
```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
```

#### 1.2 application.yml 설정
```yaml
spring:
  thymeleaf:
    cache: false            # 개발 환경에서는 캐시 비활성화
    prefix: classpath:/templates/  # 템플릿 위치
    suffix: .html           # 템플릿 확장자
    mode: HTML             # 템플릿 모드
    encoding: UTF-8        # 인코딩
```

### 2. 레이아웃 구성

#### 2.1 기본 레이아웃 (layout.html)
```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <meta charset="UTF-8">
    <title layout:title-pattern="$CONTENT_TITLE - 배치 모니터링">기본 타이틀</title>
    
    <!-- 공통 CSS -->
    <link rel="stylesheet" th:href="@{/css/common.css}">
    <link rel="stylesheet" th:href="@{/css/layout.css}">
    
    <!-- 페이지별 CSS 영역 -->
    <th:block layout:fragment="css"></th:block>
</head>
<body>
    <!-- 헤더 영역 -->
    <header th:replace="fragments/header :: header"></header>
    
    <!-- 사이드바 영역 -->
    <aside th:replace="fragments/sidebar :: sidebar"></aside>
    
    <!-- 본문 영역 -->
    <main layout:fragment="content">
        <!-- 각 페이지의 내용이 여기에 들어감 -->
    </main>
    
    <!-- 푸터 영역 -->
    <footer th:replace="fragments/footer :: footer"></footer>
    
    <!-- 공통 JavaScript -->
    <script th:src="@{/js/common.js}"></script>
    
    <!-- 페이지별 JavaScript 영역 -->
    <th:block layout:fragment="script"></th:block>
</body>
</html>
```

#### 2.2 페이지 구현 예시 (batch-jobs.html)
```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout}">
<head>
    <title>배치 작업 목록</title>
    <!-- 페이지별 CSS -->
    <th:block layout:fragment="css">
        <link rel="stylesheet" th:href="@{/css/batch-jobs.css}">
    </th:block>
</head>
<body>
    <!-- 본문 영역 -->
    <main layout:fragment="content">
        <h2>배치 작업 목록</h2>
        <div class="job-list">
            <table>
                <thead>
                    <tr>
                        <th>작업명</th>
                        <th>상태</th>
                        <th>마지막 실행</th>
                        <th>다음 실행</th>
                        <th>작업</th>
                    </tr>
                </thead>
                <tbody>
                    <tr th:each="job : ${jobs}">
                        <td th:text="${job.name}">작업명</td>
                        <td th:text="${job.status}">상태</td>
                        <td th:text="${#temporals.format(job.lastExecuted, 'yyyy-MM-dd HH:mm:ss')}">
                            마지막 실행
                        </td>
                        <td th:text="${#temporals.format(job.nextExecute, 'yyyy-MM-dd HH:mm:ss')}">
                            다음 실행
                        </td>
                        <td>
                            <button th:onclick="'executeJob(\'' + ${job.id} + '\')'">실행</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </main>
    
    <!-- 페이지별 JavaScript -->
    <th:block layout:fragment="script">
        <script th:src="@{/js/batch-jobs.js}"></script>
    </th:block>
</body>
</html>
```

### 3. Thymeleaf 문법 가이드

#### 3.1 기본 표현식
```html
<!-- 텍스트 출력 -->
<span th:text="${message}">기본 메시지</span>

<!-- HTML 출력 -->
<div th:utext="${htmlContent}">기본 HTML</div>

<!-- 변수 표현식 -->
<span th:text="${user.name}">사용자명</span>

<!-- URL 표현식 -->
<a th:href="@{/jobs/{id}(id=${job.id})}">상세보기</a>

<!-- 조건부 표현식 -->
<div th:if="${not #lists.isEmpty(jobs)}">
    작업이 있습니다.
</div>
<div th:unless="${not #lists.isEmpty(jobs)}">
    작업이 없습니다.
</div>

<!-- 반복문 -->
<tr th:each="job, stat : ${jobs}">
    <td th:text="${stat.count}">1</td>
    <td th:text="${job.name}">작업명</td>
</tr>

<!-- Switch-Case -->
<div th:switch="${job.status}">
    <p th:case="'RUNNING'">실행 중</p>
    <p th:case="'COMPLETED'">완료</p>
    <p th:case="*">기타</p>
</div>
```

#### 3.2 유틸리티 객체
```html
<!-- 날짜 포맷팅 -->
<span th:text="${#temporals.format(job.startTime, 'yyyy-MM-dd HH:mm:ss')}">
    2024-01-01 12:00:00
</span>

<!-- 숫자 포맷팅 -->
<span th:text="${#numbers.formatDecimal(amount, 1, 'COMMA', 2, 'POINT')}">
    1,234.56
</span>

<!-- 문자열 처리 -->
<span th:text="${#strings.abbreviate(description, 100)}">
    긴 설명...
</span>

<!-- 컬렉션 처리 -->
<div th:if="${#lists.isEmpty(jobs)}">
    작업이 없습니다.
</div>
```

#### 3.3 폼 처리
```html
<!-- 폼 바인딩 -->
<form th:action="@{/jobs}" th:object="${jobForm}" method="post">
    <div>
        <label>작업명:</label>
        <input type="text" th:field="*{name}">
        <span th:if="${#fields.hasErrors('name')}" 
              th:errors="*{name}" 
              class="error">
            이름 오류
        </span>
    </div>
    
    <div>
        <label>Cron 표현식:</label>
        <input type="text" th:field="*{cronExpression}">
        <span th:if="${#fields.hasErrors('cronExpression')}" 
              th:errors="*{cronExpression}" 
              class="error">
            Cron 표현식 오류
        </span>
    </div>
    
    <button type="submit">저장</button>
</form>
```

### 4. 공통 컴포넌트

#### 4.1 페이지네이션 (fragments/pagination.html)
```html
<div th:fragment="pagination (page)">
    <div class="pagination" 
         th:if="${page.totalPages > 0}">
        <!-- 이전 페이지 -->
        <a th:href="@{${#httpServletRequest.requestURI}(page=${page.number - 1})}"
           th:class="${page.first} ? 'disabled' : ''"
           th:if="${not page.first}">
            이전
        </a>
        
        <!-- 페이지 번호 -->
        <span th:each="pageNum : ${#numbers.sequence(0, page.totalPages - 1)}">
            <a th:href="@{${#httpServletRequest.requestURI}(page=${pageNum})}"
               th:text="${pageNum + 1}"
               th:class="${pageNum == page.number} ? 'active' : ''">
                1
            </a>
        </span>
        
        <!-- 다음 페이지 -->
        <a th:href="@{${#httpServletRequest.requestURI}(page=${page.number + 1})}"
           th:class="${page.last} ? 'disabled' : ''"
           th:if="${not page.last}">
            다음
        </a>
    </div>
</div>
```

#### 4.2 알림 메시지 (fragments/alert.html)
```html
<div th:fragment="alert (type, message)">
    <div th:if="${message != null}"
         th:class="'alert alert-' + ${type}">
        <span th:text="${message}">알림 메시지</span>
        <button type="button" class="close" data-dismiss="alert">&times;</button>
    </div>
</div>
```

### 5. JavaScript 연동

#### 5.1 타임리프와 JavaScript 데이터 전달
```html
<!-- 단일 값 전달 -->
<script th:inline="javascript">
    const message = /*[[${message}]]*/ '기본 메시지';
    
    // 객체 전달
    const job = /*[[${job}]]*/ {};
    
    // 배열 전달
    const jobs = /*[[${jobs}]]*/ [];
</script>

<!-- AJAX 호출 예시 -->
<script th:inline="javascript">
    function executeJob(jobId) {
        const csrfToken = /*[[${_csrf.token}]]*/ '';
        const csrfHeader = /*[[${_csrf.headerName}]]*/ 'X-CSRF-TOKEN';
        
        fetch(`/api/jobs/${jobId}/execute`, {
            method: 'POST',
            headers: {
                [csrfHeader]: csrfToken
            }
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                alert('작업이 실행되었습니다.');
                location.reload();
            } else {
                alert('작업 실행에 실패했습니다.');
            }
        });
    }
</script>
```

### 6. 보안 처리

#### 6.1 Spring Security 통합
```html
<!-- 인증 여부 확인 -->
<div th:if="${#authorization.expression('isAuthenticated()')}">
    <span th:text="${#authentication.name}">사용자명</span>
</div>

<!-- 권한 기반 표시 -->
<div th:if="${#authorization.expression('hasRole(''ADMIN'')')}">
    관리자 메뉴
</div>

<!-- CSRF 토큰 -->
<form th:action="@{/jobs}" method="post">
    <input type="hidden" 
           th:name="${_csrf.parameterName}" 
           th:value="${_csrf.token}" />
    <!-- 폼 내용 -->
</form>
```

### 7. 에러 페이지

#### 7.1 에러 페이지 구현 (error.html)
```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout}">
<head>
    <title>에러 발생</title>
</head>
<body>
    <main layout:fragment="content">
        <div class="error-container">
            <h2>오류가 발생했습니다</h2>
            <div class="error-details">
                <p>
                    <strong>상태:</strong> 
                    <span th:text="${status}">500</span>
                </p>
                <p>
                    <strong>메시지:</strong> 
                    <span th:text="${error}">Internal Server Error</span>
                </p>
                <div th:if="${trace}" class="error-trace">
                    <pre th:text="${trace}">스택 트레이스</pre>
                </div>
            </div>
            <a th:href="@{/}" class="btn btn-primary">홈으로 돌아가기</a>
        </div>
    </main>
</body>
</html>
```

### 8. 성능 최적화

#### 8.1 캐시 설정
```yaml
spring:
  thymeleaf:
    cache: true  # 운영 환경에서는 캐시 활성화
```

#### 8.2 프래그먼트 캐싱
```html
<!-- 캐시 키 지정 -->
<div th:fragment="userInfo (user)" 
     th:cache="${user.id}">
    <!-- 사용자 정보 표시 -->
</div>
```

### 9. 국제화 (i18n)

#### 9.1 메시지 설정
```yaml
spring:
  messages:
    basename: messages
    encoding: UTF-8
```

#### 9.2 메시지 사용
```html
<!-- 메시지 출력 -->
<h1 th:text="#{page.title}">제목</h1>

<!-- 파라미터가 있는 메시지 -->
<p th:text="#{message.welcome(${user.name})}">환영합니다</p>

<!-- 언어 선택 -->
<div class="language-selector">
    <a th:href="@{''(lang=ko)}">한국어</a>
    <a th:href="@{''(lang=en)}">English</a>
</div>
```

### 10. 모범 사례

#### 10.1 코드 구조화
```text
templates/
├── layouts/
│   └── layout.html
├── fragments/
│   ├── header.html
│   ├── footer.html
│   ├── sidebar.html
│   ├── pagination.html
│   └── alert.html
├── batch/
│   ├── list.html
│   ├── detail.html
│   └── form.html
├── error/
│   ├── 404.html
│   ├── 500.html
│   └── error.html
└── index.html
```

#### 10.2 네이밍 컨벤션
- 레이아웃 템플릿: layout.html
- 페이지 템플릿: 기능명/동작.html (예: batch/list.html)
- 프래그먼트: fragments/컴포넌트명.html
- 에러 페이지: error/에러코드.html

#### 10.3 주석 처리
```html
<!-- /* 타임리프 주석 - 클라이언트에 전송되지 않음 */ -->
<!--/* 여러 줄 
      타임리프 주석 */-->

<!-- 일반 HTML 주석 - 클라이언트에 전송됨 -->
```
