You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
clean-parking/README.md

1613 lines
58 KiB
Markdown

# XIT Framework 개발 가이드
## 목차
- [1. 프로젝트 개요](#1-프로젝트-개요)
- [2. 기술 스택 및 라이브러리 버전](#2-기술-스택-및-라이브러리-버전)
- [2.1 핵심 기술 스택](#tech-stack)
- [2.2 주요 라이브러리](#main-libraries)
- [3. 프로젝트 구조](#3-프로젝트-구조)
- [3.1 디렉토리 구조](#directory-structure)
- [3.2 패키지 구조](#package-structure)
- [4. 주요 설정 파일](#4-주요-설정-파일)
- [4.1 application.yml](#application-yml)
- [4.2 application-local.yml](#application-local-yml)
- [4.3 application-dev.yml](#application-dev-yml)
- [4.4 application-prd.yml](#application-prd-yml)
- [4.5 build.gradle](#build-gradle)
- [4.6 logback-spring.xml](#logback-spring-xml)
- [4.7 mybatis-config.xml](#mybatis-config-xml)
- [4.8 데이터베이스 스키마](#database-schema)
- [5. 주요 기능 및 사용법](#5-주요-기능-및-사용법)
- [5.1 프로젝트 구조 패턴](#project-structure-pattern)
- [5.2 사용자 관리 기능 예시](#user-management-example)
- [5.2.1 Controller](#controller)
- [5.2.2 Service](#service)
- [5.2.3 Mapper](#mapper)
- [5.3 로그인 기능](#login-function)
- [5.3.1 Controller](#login-controller)
- [5.4 공통 유틸리티](#common-utilities)
- [5.4.1 API 응답 유틸리티](#api-response-util)
- [5.5 레이아웃 구성](#layout-configuration)
- [5.6 보안 기능](#security-features)
- [5.6.1 XSS 필터](#xss-filter)
- [5.6.2 리퍼러 체크](#referer-check)
- [5.6.3 권한 관리](#permission-management)
- [5.6.4 사용자 > 그룹 > 역할 > 메뉴 구조](#user-group-role-menu-structure)
- [5.7 배치 작업 관리](#batch-management)
- [6. 개발 가이드라인](#6-개발-가이드라인)
- [6.1 코드 작성 규칙](#code-writing-rules)
- [6.2 디렉토리 구조 가이드](#directory-structure-guide)
- [6.3 UI 컴포넌트 가이드](#ui-component-guide)
- [7. 배포 가이드](#7-배포-가이드)
- [7.1 빌드 방법](#build-method)
- [7.1.1 WAR 파일 빌드](#war-file-build)
- [7.1.2 bootWar 파일 빌드](#bootwar-file-build)
- [7.2 프로필 설정](#profile-settings)
- [7.2.1 기본 프로필](#default-profile)
- [7.2.2 프로필 활성화 방법](#profile-activation)
- [7.3 외부 WAS를 이용한 배포](#external-was-deployment)
- [7.3.1 Tomcat에 WAR 파일 배포](#tomcat-war-deployment)
- [7.3.2 외부 WAS에서 프로필 설정](#external-was-profile-settings)
- [7.4 WAR로 배포 및 실행](#war-deployment-execution)
- [7.4.1 bootWar 파일 실행](#bootwar-execution)
- [7.4.2 외부 WAS에 배포하여 실행](#external-was-execution)
- [7.4.3 백그라운드 실행 (Linux/macOS)](#background-execution)
- [7.4.4 Windows 서비스로 등록](#windows-service-registration)
- [7.5 배포 환경 설정](#deployment-environment-settings)
- [7.6 배포 체크리스트](#deployment-checklist)
- [8. 참고 자료](#8-참고-자료)
## 1. 프로젝트 개요
XIT Framework는 Spring Boot 기반의 웹 애플리케이션 프레임워크로, 전자정부 프레임워크를 확장하여 개발된 프로젝트입니다. 이 문서는 XIT Framework의 구조, 기술 스택, 주요 기능 및 사용법에 대한 종합적인 가이드를 제공합니다.
## 2. 기술 스택 및 라이브러리 버전
<h3 id="tech-stack">2.1 핵심 기술 스택</h3>
| 기술 | 버전 | 설명 |
|------|------|------|
| Java | 개발: 1.8, 배포: 1.8 | 자바 개발 및 실행 환경 |
| Spring Boot | 2.7.18 | 스프링 기반 애플리케이션 개발 프레임워크 |
| 전자정부 프레임워크 | 4.3.0 | 한국 정부 표준 웹 개발 프레임워크 |
| Servlet | 3.1 | 웹 애플리케이션 표준 |
| Gradle | - | 빌드 및 의존성 관리 도구 |
| MariaDB | - | 관계형 데이터베이스 |
<h3 id="main-libraries">2.2 주요 라이브러리</h3>
| 라이브러리 | 버전 | 설명 |
|------------|------|------|
| MyBatis | 2.3.1 | SQL 매핑 프레임워크 |
| Apache Tiles | 3.0.8 | 레이아웃 템플릿 엔진 |
| TOAST UI Grid | 4.19.2 | 자바스크립트 그리드 라이브러리 |
| Lombok | - | 자바 코드 생성 라이브러리 |
| Apache Commons Text | 1.10.0 | 텍스트 처리 유틸리티 |
| Apache POI | 5.3.0 | 엑셀 파일 처리 라이브러리 |
## 3. 프로젝트 구조
<h3 id="directory-structure">3.1 디렉토리 구조</h3>
```
xit-framework/
├── DB-DDL/ # 데이터베이스 스크립트
│ └── maria/ # MariaDB 스크립트
│ ├── ddl/ # 테이블 정의 스크립트
│ └── dml/ # 샘플 데이터 스크립트
├── src/
│ ├── main/
│ │ ├── java/ # 자바 소스 코드
│ │ │ ├── egovframework/ # 전자정부 프레임워크 확장 코드
│ │ │ └── go/kr/project/ # 프로젝트 소스 코드
│ │ ├── resources/ # 리소스 파일
│ │ │ ├── mybatis/ # MyBatis 설정 및 매퍼
│ │ │ └── application.yml # 애플리케이션 설정 파일
│ │ └── webapp/ # 웹 리소스
│ │ ├── resources/ # 정적 리소스 (CSS, JS, 이미지 등)
│ │ └── WEB-INF/views/ # JSP 뷰 파일
│ └── test/ # 테스트 코드
└── build.gradle # Gradle 빌드 스크립트
```
<h3 id="package-structure">3.2 패키지 구조</h3>
```
go.kr.project/
├── common/ # 공통 컴포넌트
├── login/ # 로그인 관련 기능
│ ├── controller/ # 컨트롤러 클래스
│ ├── mapper/ # MyBatis 매퍼 인터페이스
│ ├── model/ # 데이터 모델 클래스
│ └── service/ # 서비스 클래스
└── system/ # 시스템 관리 기능
├── auth/ # 권한 관리
├── code/ # 코드 관리
├── group/ # 그룹 관리
├── menu/ # 메뉴 관리
├── role/ # 역할 관리
└── user/ # 사용자 관리
├── controller/ # 컨트롤러 클래스
├── mapper/ # MyBatis 매퍼 인터페이스
├── model/ # 데이터 모델 클래스
└── service/ # 서비스 클래스
egovframework/
├── config/ # 프레임워크 설정
├── exception/ # 예외 처리
├── filter/ # 필터
├── interceptor/ # 인터셉터
└── util/ # 유틸리티 클래스
```
## 4. 주요 설정 파일
<h3 id="application-yml">4.1 application.yml</h3>
애플리케이션의 공통 설정을 정의하는 파일입니다.
```yaml
# 공통 설정
spring:
profiles:
active: local
application:
name: xit-framework
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
# 데이터베이스 타입 설정
Globals:
DbType: maria
# MyBatis 설정
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/**/*_${Globals.DbType}.xml
type-aliases-package: go.kr.project.**.model,egovframework.**.model
# 서버 설정
server:
port: 8080
servlet:
context-path: /
encoding:
charset: UTF-8
multipart:
max-file-size: 10MB
max-request-size: 50MB
# Swagger UI 설정
springdoc:
api-docs:
enabled: true
swagger-ui:
enabled: true
path: /swagger-ui.html
operations-sorter: method
tags-sorter: alpha
display-request-duration: true
doc-expansion: none
disable-swagger-default-url: true
filter: true
supportedSubmitMethods: [get, post]
packages-to-scan: go.kr.project
paths-to-match: /**
default-consumes-media-type: application/json
default-produces-media-type: application/json
model-converters:
pageable-converter:
enabled: true
# 로그인 설정
login:
url: /login/login.do
lock:
count: 5
DefaultPassword: xitpassword
allowMultipleLogin: true
# 인터셉터 설정
interceptor:
interceptorExclude:
- /html/*.html
- /login/**
- /
- /.well-known/**
- /common/**
- /resources/**
- /css/**
- /img/**
- /js/**
- /xit/**
- /plugins/**
- /font/**
- /error/**
- /favicon.ico
- /swagger-ui/**
- /v3/api-docs/**
refererExclude:
- /**
- /html/*.html
- /login/**
- /
- /main.do
- /error/**
- /common/**
- /swagger-ui/**
- /v3/api-docs/**
```
<h3 id="application-local-yml">4.2 application-local.yml</h3>
로컬 개발 환경에 대한 설정을 정의하는 파일입니다.
```yaml
# 로컬 프로필 설정
spring:
config:
activate:
on-profile: local
devtools:
livereload:
enabled: true
restart:
enabled: true
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://211.119.124.9:4407/xitframework?characterEncoding=UTF-8&serverTimezone=Asia/Seoul
username: egov
password: xit1807
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
validation-timeout: 60000
# 서버 설정
server:
servlet:
session:
timeout: 30m
tracking-modes: cookie
persistent: false
# 로깅 설정
logging:
config: classpath:logback-spring.xml
file:
path: d:/data/xit-framework/logs
name: xit-framework
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
level:
go.kr.project: DEBUG
egovframework: DEBUG
org.mybatis: DEBUG
org.springframework: INFO
# 파일 업로드 설정
file:
upload:
path: D:\xit-framework-file
max-size: 10
max-total-size: 50
max-files: 10
allowed-extensions: hwp,jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip
real-file-delete: true
sub-dirs:
bbs-notice: bbs/notice
bbs-post: bbs/post
html-editor: common/html_editor
```
<h3 id="application-dev-yml">4.3 application-dev.yml</h3>
개발 환경에 대한 설정을 정의하는 파일입니다.
```yaml
# 개발 프로필 설정
spring:
config:
activate:
on-profile: dev
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://211.119.124.9:4407/xitframework?characterEncoding=UTF-8&serverTimezone=Asia/Seoul
username: egov
password: xit1807
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
validation-timeout: 60000
# 서버 설정
server:
servlet:
session:
timeout: 30m
# 로깅 설정
logging:
config: classpath:logback-spring.xml
file:
path: d:/data/xit-framework/logs
name: xit-framework
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
level:
go.kr.project: DEBUG
egovframework: DEBUG
org.mybatis: DEBUG
org.springframework: INFO
# 파일 업로드 설정
file:
upload:
path: D:\xit-framework-file
max-size: 10
max-total-size: 50
max-files: 10
allowed-extensions: hwp,jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip
real-file-delete: true
sub-dirs:
bbs-notice: bbs/notice
bbs-post: bbs/post
html-editor: common/html_editor
```
<h3 id="application-prd-yml">4.4 application-prd.yml</h3>
운영 환경에 대한 설정을 정의하는 파일입니다.
```yaml
# 운영 프로필 설정
spring:
config:
activate:
on-profile: prd
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://211.119.124.9:4407/xitframework?characterEncoding=UTF-8&serverTimezone=Asia/Seoul
username: egov
password: xit1807
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
validation-timeout: 60000
# 서버 설정
server:
servlet:
session:
timeout: 60m
# 로깅 설정
logging:
config: classpath:logback-spring.xml
file:
path: /data/logs/xit-framework-file
name: xit-framework
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
level:
go.kr.project: WARN
egovframework: WARN
org.mybatis: WARN
org.springframework: WARN
# 파일 업로드 설정
file:
upload:
path: /data/upload/xit-framework-file
max-size: 10
max-total-size: 50
max-files: 10
allowed-extensions: hwp,jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip
real-file-delete: true
sub-dirs:
bbs-notice: bbs/notice
bbs-post: bbs/post
html-editor: common/html_editor
```
<h3 id="build-gradle">4.5 build.gradle</h3>
프로젝트의 빌드 및 의존성을 정의하는 파일입니다.
```gradle
plugins {
id 'org.springframework.boot' version '2.7.18'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
id 'war'
}
group = 'go.kr.project'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
mavenCentral()
maven { url 'https://maven.egovframe.go.kr/maven/' }
}
ext {
tomcatVersion = '9.0.78'
tilesVersion = '3.0.8'
mybatisVersion = '2.3.1'
commonsTextVersion = '1.10.0'
egovFrameVersion = '4.3.0'
}
dependencies {
// 스프링 부트 의존성
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// 전자정부 프레임워크 의존성
implementation("org.egovframe.rte:org.egovframe.rte.fdl.cmmn:${egovFrameVersion}") {
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl'
}
implementation("org.egovframe.rte:org.egovframe.rte.ptl.mvc:${egovFrameVersion}") {
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl'
}
// 로깅 의존성
implementation 'ch.qos.logback:logback-classic'
implementation 'ch.qos.logback:logback-core'
implementation 'org.slf4j:slf4j-api'
// 서블릿 & JSP 관련 의존성
implementation 'javax.servlet:jstl'
// 톰캣 설정
implementation "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}"
implementation "org.apache.tomcat.embed:tomcat-embed-el:${tomcatVersion}"
implementation "org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
// 타일즈 의존성
implementation "org.apache.tiles:tiles-jsp:${tilesVersion}"
implementation "org.apache.tiles:tiles-core:${tilesVersion}"
implementation "org.apache.tiles:tiles-api:${tilesVersion}"
implementation "org.apache.tiles:tiles-servlet:${tilesVersion}"
implementation "org.apache.tiles:tiles-el:${tilesVersion}"
// 데이터 액세스 의존성
implementation "org.mybatis.spring.boot:mybatis-spring-boot-starter:${mybatisVersion}"
implementation 'org.mariadb.jdbc:mariadb-java-client'
// 유틸리티 의존성
implementation "org.apache.commons:commons-text:${commonsTextVersion}"
// EXCEL
implementation 'org.apache.poi:poi:5.3.0'
implementation 'org.apache.poi:poi-ooxml:5.3.0'
// Swagger UI
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'
implementation 'org.springdoc:springdoc-openapi-webmvc-core:1.7.0'
// 개발 도구 의존성
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
// 테스트 의존성
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// 배포 관련 의존성
providedCompile "org.apache.tomcat:tomcat-servlet-api:${tomcatVersion}"
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}
tasks.named('test') {
useJUnitPlatform()
}
war {
archiveFileName = 'xit-framework.war'
}
bootWar {
archiveFileName = 'xit-framework-boot.war'
}
```
<h3 id="logback-spring-xml">4.6 logback-spring.xml</h3>
로깅 설정을 정의하는 파일입니다.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 기본 로그 경로 및 파일명 설정 -->
<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="logs" />
<springProperty scope="context" name="LOG_FILE" source="logging.file.name" defaultValue="application" />
<springProperty scope="context" name="MAX_FILE_SIZE" source="logging.logback.rollingpolicy.max-file-size" defaultValue="10MB" />
<springProperty scope="context" name="MAX_HISTORY" source="logging.logback.rollingpolicy.max-history" defaultValue="30" />
<!-- 콘솔 출력 설정 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 컬러 콘솔 출력 설정 (개발 환경용) -->
<appender name="CONSOLE_COLOR" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>
<!-- 파일 출력 설정 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>${MAX_HISTORY}</maxHistory>
</rollingPolicy>
<prudent>true</prudent>
</appender>
<!-- 비동기 파일 출력 설정 -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>false</includeCallerData>
<neverBlock>false</neverBlock>
</appender>
<!-- 환경별 설정 -->
<springProfile name="local">
<root level="INFO">
<appender-ref ref="CONSOLE_COLOR" />
<appender-ref ref="ASYNC_FILE" />
</root>
</springProfile>
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE_COLOR" />
<appender-ref ref="ASYNC_FILE" />
</root>
</springProfile>
<springProfile name="prd">
<root level="WARN">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ASYNC_FILE" />
</root>
</springProfile>
</configuration>
```
<h3 id="mybatis-config-xml">4.7 mybatis-config.xml</h3>
MyBatis 설정을 정의하는 파일입니다.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- Java 속성은 camelCase, 데이터베이스 컬럼은 snake_case 사용 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="useColumnLabel" value="true"/>
<!-- PreparedStatement 캐시 -->
<setting name="defaultExecutorType" value="REUSE"/>
<!-- 지연 로딩 비활성화 -->
<setting name="lazyLoadingEnabled" value="false"/>
<setting name="cacheEnabled" value="false"/>
<!-- SQL 로깅 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<typeAliases>
<!-- Type aliases는 application.yml의 type-aliases-package에서 정의 -->
</typeAliases>
</configuration>
```
<h3 id="database-schema">4.8 데이터베이스 스키마</h3>
주요 테이블 구조는 `DB-DDL/maria/all_ddl.sql`에 정의되어 있습니다:
- **tb_user**: 사용자 정보
- **tb_group**: 그룹 정보
- **tb_role**: 역할 정보
- **tb_menu**: 메뉴 정보
- **tb_group_role**: 그룹-역할 매핑
- **tb_role_menu**: 역할-메뉴 매핑
- **tb_code_group**: 코드 그룹
- **tb_code_detail**: 코드 상세
- **tb_bbs_config**: 게시판 설정
- **tb_bbs_post**: 게시물
- **tb_bbs_file**: 게시판 파일
- **tb_user_session**: 사용자 세션
- **tb_login_log**: 로그인 로그
MyBatis 매퍼 파일은 `src/main/resources/mybatis/mapper/` 디렉토리에 모듈별로 구성되어 있습니다.
## 5. 주요 기능 및 사용법
<h3 id="project-structure-pattern">5.1 프로젝트 구조 패턴</h3>
XIT Framework는 MVC(Model-View-Controller) 패턴을 기반으로 하며, 각 기능별로 다음과 같은 계층 구조를 가집니다:
1. **Controller**: 클라이언트 요청을 처리하고 응답을 반환합니다.
2. **Service**: 비즈니스 로직을 처리합니다.
3. **Mapper**: 데이터베이스 접근을 담당합니다.
4. **Model**: 데이터 구조를 정의합니다.
<h3 id="user-management-example">5.2 사용자 관리 기능 예시</h3>
사용자 관리 기능은 다음과 같은 구조로 구현되어 있습니다:
<h4 id="controller">5.2.1 Controller</h4>
```java
@Controller
@RequestMapping("/system/user")
public class UserController {
@Resource(name = "UserService")
private UserService userService;
// 사용자 목록 페이지
@RequestMapping("/list.do")
public String userList(SystemUserVO paramVO, Model model) {
return "system/user/list.base";
}
// 사용자 목록 조회 AJAX
@PostMapping("/list.ajax")
public ResponseEntity<?> getUserListAjax(@ModelAttribute SystemUserVO paramVO) {
int totalCount = userService.selectUserListTotalCount(paramVO);
paramVO.setTotalCount(totalCount);
paramVO.setPagingYn("Y");
List<SystemUserVO> userList = userService.selectUserList(paramVO);
return ApiResponseUtil.successWithGrid(userList, paramVO);
}
// 사용자 등록 페이지
@GetMapping("/register.do")
public String registerUser(Model model) {
model.addAttribute("user", new SystemUserVO());
return "system/user/form.base";
}
// 사용자 등록 처리 AJAX
@PostMapping("/register.ajax")
public ResponseEntity<?> registerUserAjax(@ModelAttribute SystemUserVO userVO) {
// 사용자 ID 자동 생성
String userId = userService.generateUserId();
userVO.setUserId(userId);
// 기본 비밀번호 설정
String defaultPassword = env.getProperty("Globals.DefaultPassword", "xitpassword");
userVO.setPasswd(EgovFileScrty.encryptPassword(defaultPassword, userId));
int result = userService.insertUser(userVO);
if (result > 0) {
return ApiResponseUtil.success("사용자가 성공적으로 등록되었습니다.");
} else {
return ApiResponseUtil.error("사용자 등록에 실패했습니다.");
}
}
}
```
<h4 id="service">5.2.2 Service</h4>
```java
@Service("UserService")
public interface UserService {
// 사용자 정보 조회
SystemUserVO selectUser(String userId);
// 사용자 목록 조회
List<SystemUserVO> selectUserList(SystemUserVO vo);
// 사용자 목록 총 개수 조회
int selectUserListTotalCount(SystemUserVO vo);
// 사용자 ID 생성
String generateUserId();
// 사용자 등록
int insertUser(SystemUserVO vo);
// 사용자 수정
int updateUser(SystemUserVO vo);
}
```
<h4 id="mapper">5.2.3 Mapper</h4>
```java
@Mapper
public interface UserMapper {
// 사용자 정보 조회
SystemUserVO selectUser(String userId);
// 사용자 목록 조회
List<SystemUserVO> selectUserList(SystemUserVO vo);
// 사용자 목록 총 개수 조회
int selectUserListTotalCount(SystemUserVO vo);
// 사용자 ID 생성
String generateUserId();
// 사용자 등록
int insertUser(SystemUserVO vo);
// 사용자 수정
int updateUser(SystemUserVO vo);
}
```
<h3 id="login-function">5.3 로그인 기능</h3>
로그인 기능은 다음과 같은 구조로 구현되어 있습니다:
<h4 id="login-controller">5.3.1 Controller</h4>
```java
@Controller
@RequestMapping("/login")
public class LoginController {
@Autowired
private LoginService loginService;
// 로그인 페이지
@RequestMapping(value = "/login.do", method = RequestMethod.GET)
public String loginPage(Model model, HttpServletRequest request) {
// 쿠키에서 저장된 아이디 가져오기
Cookie[] cookies = request.getCookies();
String savedUserId = "";
boolean isSaveId = false;
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("savedUserId".equals(cookie.getName())) {
savedUserId = cookie.getValue();
isSaveId = true;
break;
}
}
}
model.addAttribute("savedUserId", savedUserId);
model.addAttribute("isSaveId", isSaveId);
return "login/login.login";
}
// 로그인 처리 AJAX
@PostMapping("/login.ajax")
public ResponseEntity<?> loginAjax(
@RequestParam("userAcnt") String userAcnt,
@RequestParam("passwd") String passwd,
@RequestParam(value = "saveId", required = false) String saveId,
HttpServletRequest request,
HttpServletResponse response) {
try {
// 로그인 처리
SessionVO sessionVO = loginService.login(userAcnt, passwd, request, response);
// 아이디 저장 처리
if ("Y".equals(saveId)) {
Cookie cookie = new Cookie("savedUserId", userAcnt);
cookie.setMaxAge(60 * 60 * 24 * 7);
cookie.setPath("/");
response.addCookie(cookie);
} else {
Cookie cookie = new Cookie("savedUserId", "");
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);
}
if (sessionVO != null) {
// 로그인 성공
Map<String, Object> data = new HashMap<>();
data.put("redirectUrl", "/main.do");
return ApiResponseUtil.success(data, "로그인에 성공하였습니다.");
} else {
// 로그인 실패
return ApiResponseUtil.error("아이디 또는 비밀번호가 일치하지 않습니다.");
}
} catch (Exception e) {
return ApiResponseUtil.error("로그인 처리 중 오류가 발생했습니다.");
}
}
// 로그아웃 처리
@GetMapping("/logout.do")
public String logout(HttpServletRequest request, HttpServletResponse response) {
try {
loginService.logout(request, response);
} catch (Exception e) {
log.error("로그아웃 처리 중 오류 발생", e);
}
return "redirect:" + loginProperties.getUrl();
}
}
```
<h3 id="common-utilities">5.4 공통 유틸리티</h3>
<h4 id="api-response-util">5.4.1 API 응답 유틸리티</h4>
```java
public class ApiResponseUtil {
// 성공 응답
public static ResponseEntity<?> success(Object data, String message) {
ApiResponseEntity response = new ApiResponseEntity();
response.setResult(true);
response.setMessage(message);
response.setData(data);
return ResponseEntity.ok(response);
}
// 성공 응답 (메시지만)
public static ResponseEntity<?> success(String message) {
return success(null, message);
}
// 그리드 데이터 응답
public static ResponseEntity<?> successWithGrid(List<?> data, PagingVO pagingVO) {
Map<String, Object> gridData = new HashMap<>();
gridData.put("contents", data);
gridData.put("pagination", pagingVO);
return success(gridData, "조회가 완료되었습니다.");
}
// 오류 응답
public static ResponseEntity<?> error(String message) {
ApiResponseEntity response = new ApiResponseEntity();
response.setResult(false);
response.setMessage(message);
return ResponseEntity.ok(response);
}
}
```
<h3 id="layout-configuration">5.5 레이아웃 구성</h3>
XIT Framework는 Apache Tiles를 사용하여 레이아웃을 구성합니다. 기본 레이아웃은 `src/main/webapp/WEB-INF/views/layouts/base/default.jsp`에 정의되어 있습니다.
```jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<!-- Cache Control -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<title>XIT - Framework</title>
<link rel="shortcut icon" type="image/x-icon" href="<c:url value='/img/common/favicon.ico' />">
<!-- Font & Icon -->
<link rel="stylesheet" href="<c:url value='/font/roboto/roboto.min.css' />" id="font-css">
<link rel="stylesheet" href="<c:url value='/plugins/material-design-icons-iconfont/material-design-icons.min.css' />">
<link rel="stylesheet" href="<c:url value='/plugins/fontawesome-free/css/all.min.css' />">
<link rel="stylesheet" href="<c:url value='/css/materialdesignicons.css' />">
<!-- Plugins -->
<link rel="stylesheet" href="<c:url value='/plugins/DataTables/datatables.css' />">
<!-- Main Style -->
<link rel="stylesheet" href="<c:url value='/plugins/simplebar/simplebar.min.css' />">
<link rel="stylesheet" href="<c:url value='/css/style.min.css' />" id="main-css">
<link rel="stylesheet" href="<c:url value='/css/sidebar-royal.min.css' />" id="theme-css">
<link rel="stylesheet" href="<c:url value='/css/bootstrap-datepicker.min.css' />">
<link rel="stylesheet" href="<c:url value='/css/style_new.css' />">
<!-- Main Scripts -->
<script type="text/javascript" src="<c:url value='/js/jquery.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/bootstrap.bundle.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/popper/1.16.1/popper.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/bootstrap-datepicker.min.js' />"></script>
<!-- Plugins -->
<script type="text/javascript" src="<c:url value='/plugins/simplebar/simplebar.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/feather-icons/feather.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/DataTables/datatables.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/crypto-js/crypto-js-4.0.0.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/inputmask-4.x/jquery.inputmask.bundle.js' />"></script>
<!-- TOAST UI Grid -->
<link rel="stylesheet" type="text/css" href="<c:url value='/plugins/tuiGrid/tui-date-picker.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/plugins/tuiGrid/tui-pagination.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/plugins/tuiGrid/tui-time-picker.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/plugins/tuiGrid/tui-grid.css' />">
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-code-snippet.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-pagination.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-date-picker.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-time-picker.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-grid.js' />"></script>
<!-- XIT 커스텀 -->
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/xit-common.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/xit-tui-grid.css' />">
<script type="text/javascript" src="<c:url value='/xit/xit-tui-grid.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-common.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-validation.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/menu-path.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/common_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/datatables_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/moment.js' />"></script>
<!-- 파일 업로드 스타일 -->
<link rel="stylesheet" href="<c:url value="/resources/css/xit-multi-fileupload.css"/>" />
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<tiles:insertAttribute name="menu_header" />
<tiles:insertAttribute name="menu" />
</div>
<!-- /Sidebar -->
<!-- Main -->
<div class="main">
<tiles:insertAttribute name="main_header" />
<tiles:insertAttribute name="main" />
</div>
<!-- /Main -->
<script type="text/javascript" src="<c:url value='/js/script.min.js' />"></script>
</body>
</html>
```
### 레이아웃 구성 요소
1. **Sidebar (사이드바)**
- `menu_header`: 메뉴 헤더 영역
- `menu`: 메뉴 네비게이션 영역
2. **Main (메인 영역)**
- `main_header`: 메인 헤더 영역
- `main`: 실제 콘텐츠 영역
### 주요 CSS/JS 라이브러리
- **Bootstrap**: 반응형 UI 프레임워크
- **Material Design Icons**: 아이콘 라이브러리
- **FontAwesome**: 아이콘 라이브러리
- **DataTables**: 데이터 테이블 플러그인
- **TOAST UI Grid**: 고성능 그리드 컴포넌트
- **SimpleBar**: 커스텀 스크롤바
- **Crypto-JS**: 암호화 라이브러리
- **InputMask**: 입력 마스킹
- **Moment.js**: 날짜/시간 처리
### XIT 커스텀 파일
- **xit-common.css/js**: 공통 스타일 및 유틸리티
- **xit-tui-grid.css/js**: TOAST UI Grid 커스터마이징
- **xit-validation.js**: 폼 유효성 검사
- **menu-path.js**: 메뉴 경로 처리
- **common_util.js**: 공통 유틸리티 함수
- **datatables_util.js**: DataTables 유틸리티
- **xit-multi-fileupload.css**: 다중 파일 업로드 스타일
### 캐시 제어
브라우저 캐시를 방지하기 위해 메타 태그와 HTTP 헤더를 설정하여 항상 최신 콘텐츠를 제공합니다.
<h3 id="security-features">5.6 보안 기능</h3>
XIT Framework는 다양한 보안 기능을 제공하여 애플리케이션의 안전성을 강화합니다.
<h4 id="xss-filter">5.6.1 XSS 필터</h4>
XSS(Cross-Site Scripting) 공격을 방지하기 위한 필터를 제공합니다. 이 필터는 모든 요청 파라미터를 검사하고 잠재적인 XSS 공격 코드를 제거합니다.
```java
// XSS 필터 설정 클래스
@Configuration
public class XssFilterConfig {
private final XssUtil xssUtil;
public XssFilterConfig(XssUtil xssUtil) {
this.xssUtil = xssUtil;
}
@Bean
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new XssFilter(xssUtil));
registrationBean.addUrlPatterns("/*"); // 모든 URL에 적용
registrationBean.setName("xssFilter");
registrationBean.setOrder(1); // 필터 순서 (낮은 숫자가 먼저 실행)
return registrationBean;
}
}
```
XSS 필터는 다음과 같은 기능을 제공합니다:
1. **HTML 태그 이스케이프**: 일반 텍스트 필드에서 HTML 태그를 이스케이프 처리합니다.
2. **HTML 에디터 내용 정화**: HTML 에디터 내용에서는 허용된 태그만 남기고 위험한 스크립트를 제거합니다.
3. **파일명 정화**: 파일명에서 위험한 문자를 제거합니다.
4. **보안 헤더 설정**: XSS 방지를 위한 HTTP 헤더를 설정합니다.
```java
// XSS 필터 구현
public class XssFilter extends OncePerRequestFilter {
private final XssUtil xssUtil;
public XssFilter(XssUtil xssUtil) {
this.xssUtil = xssUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// XSS 방지를 위한 헤더 설정
// X-XSS-Protection: 브라우저의 XSS 필터를 켜고, 공격이 감지되면 페이지를 차단.
response.setHeader("X-XSS-Protection", "1; mode=block");
// X-Content-Type-Options: 브라우저가 서버가 지정한 MIME 타입만 사용하게 하여, 잘못된 타입 해석으로 인한 보안 문제를 방지.
response.setHeader("X-Content-Type-Options", "nosniff");
// 요청을 래핑하여 XSS 필터링 적용
XssRequestWrapper xssRequestWrapper = new XssRequestWrapper(request, xssUtil);
// 필터 체인 실행
filterChain.doFilter(xssRequestWrapper, response);
}
}
```
<h4 id="referer-check">5.6.2 리퍼러 체크</h4>
리퍼러(Referer) 헤더를 검사하여 직접 URL을 입력하거나 외부 사이트에서의 접근을 제한합니다. 이를 통해 CSRF(Cross-Site Request Forgery) 공격을 방지하고 애플리케이션의 보안을 강화합니다.
```java
// AuthInterceptor 클래스의 preHandle 메서드 내부
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
// 리퍼러 체크 로직
if (!isRefererCheckExcluded(requestURI)) {
String referer = request.getHeader("Referer");
if (referer == null || referer.isEmpty()) {
// Referer 헤더가 없는 경우 (직접 URL 입력 또는 북마크 등)
log.warn("Referer 헤더 없음: {}", requestURI);
// AJAX 요청인 경우 JSON 응답 반환
if (HttpServletUtil.isAjaxRequest(request) || HttpServletUtil.isRealAjaxRequest(request)) {
handleRefererMissing(response);
return false;
}
// 일반 요청인 경우 로그인 페이지로 리다이렉트
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write("<script type='text/javascript'>");
response.getWriter().write("alert('잘못된 접근입니다. 로그인 페이지로 이동합니다.');");
response.getWriter().write("location.href = '" + request.getContextPath() + loginProperties.getUrl() + "';");
response.getWriter().write("</script>");
return false;
}
}
// 나머지 로직...
return true;
}
```
리퍼러 체크는 다음과 같은 특징을 가집니다:
1. **예외 URL 설정**: 로그인 페이지, 정적 리소스 등 일부 URL은 리퍼러 체크에서 제외됩니다.
2. **AJAX 요청 처리**: AJAX 요청의 경우 JSON 형식으로 오류 응답을 반환합니다.
3. **일반 요청 처리**: 일반 요청의 경우 경고 메시지와 함께 로그인 페이지로 리다이렉트합니다.
<h4 id="permission-management">5.6.3 권한 관리</h4>
사용자의 권한에 따라 접근 가능한 기능과 메뉴를 제한하는 권한 관리 시스템을 제공합니다. 권한 관리는 인터셉터를 통해 구현되며, 사용자의 세션 정보와 요청 URL을 기반으로 접근 권한을 검사합니다.
```java
// 권한 관리 인터셉터
@Slf4j
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
private final LoginService loginService;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final XssUtil xssUtil = new XssUtil();
@Autowired
private InterceptorProperties interceptorProperties;
@Autowired
private LoginProperties loginProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
// Referer 헤더 검사 로직...
try {
// 세션 정보 조회
SessionVO sessionVO = loginService.getSessionInfo(request);
// 세션이 없거나 로그인 상태가 아닌 경우
if (sessionVO == null || !sessionVO.isLogin()) {
// 방문자 권한 확인
if (sessionVO != null && sessionVO.isVisitor()) {
// 방문자 권한으로 접근 가능한지 확인
if (hasAccess(sessionVO, requestURI)) {
return true;
}
}
// 세션 만료 처리...
return false;
}
// 로그인 상태인 경우 접근 권한 확인
if (hasAccess(sessionVO, requestURI)) {
return true;
}
// 접근 권한이 없는 경우
log.warn("접근 권한 없음: {} - {}", sessionVO.getUser().getUserAcnt(), requestURI);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "접근 권한이 없습니다.");
return false;
} catch (Exception e) {
// 예외 처리...
return false;
}
}
// 접근 권한 확인 메서드
private boolean hasAccess(SessionVO sessionVO, String requestURI) {
List<MenuVO> menus = flattenMenuTree(sessionVO.getMenus());
if (menus.isEmpty()) {
return false;
}
// 메뉴의 URL 패턴과 일치하는지 확인
for (MenuVO menu : menus) {
if (menu.getUrlPattern() != null && !menu.getUrlPattern().isEmpty()) {
String[] patterns = xssUtil.unescape(menu.getUrlPattern()).split(",");
for (String pattern : patterns) {
if (pathMatcher.match(pattern.trim(), requestURI)) {
return true;
}
}
}
}
return false;
}
}
```
<h4 id="user-group-role-menu-structure">5.6.4 사용자 > 그룹 > 역할 > 메뉴 구조</h4>
XIT Framework는 사용자, 그룹, 역할, 메뉴를 연결하는 권한 관리 구조를 제공합니다. 이 구조는 데이터베이스 테이블 간의 관계를 통해 구현되며, 접근 제어가 가능합니다.
1. **데이터 구조 개요**
XIT Framework의 권한 관리 시스템은 다음과 같은 주요 엔티티로 구성됩니다:
- **사용자(User)**: 시스템을 사용하는 개인 계정
- **그룹(Group)**: 사용자들의 집합
- **역할(Role)**: 특정 기능에 대한 권한 집합
- **메뉴(Menu)**: 시스템의 기능 단위와 UI 구성 요소
- **URL 패턴**: 각 메뉴에 연결된 실제 접근 경로
2. **엔티티 간 관계**
- **사용자-그룹 관계**: 사용자는 하나의 그룹에 소속됩니다. 그룹은 여러 사용자를 포함할 수 있습니다. (N:1 관계)
- **그룹-역할 관계**: 그룹은 여러 역할을 가질 수 있으며, 역할은 여러 그룹에 할당될 수 있습니다. (N:M 관계)
- **역할-메뉴 관계**: 역할은 여러 메뉴에 대한 접근 권한을 가지며, 메뉴는 여러 역할에 의해 접근될 수 있습니다. (N:M 관계)
- **메뉴-URL 패턴 관계**: 메뉴는 하나 이상의 URL 패턴과 연결됩니다. (1:N 관계)
3. **데이터베이스 테이블 구조**
- **TB_USER**: 사용자 정보 저장 (USER_ID, USER_ACNT, USER_NM, PASSWD, GRP_ID 등)
- **TB_GROUP**: 그룹 정보 저장 (GRP_ID, GRP_NM, GRP_DC 등)
- **TB_ROLE**: 역할 정보 저장 (ROLE_ID, ROLE_NM, ROLE_DC 등)
- **TB_GROUP_ROLE**: 그룹-역할 매핑 정보 (GRP_ID, ROLE_ID)
- **TB_MENU**: 메뉴 정보 저장 (MENU_ID, MENU_NM, MENU_URL, URL_PATTERN, UPPER_MENU_ID 등)
- **TB_ROLE_MENU**: 역할-메뉴 매핑 정보 (ROLE_ID, MENU_ID)
4. **권한 확인 프로세스**
사용자가 특정 URL에 접근하려고 할 때, 시스템은 다음과 같은 과정으로 권한을 확인합니다:
1. 사용자 식별: 로그인한 사용자의 USER_ID 확인
2. 그룹 확인: 사용자가 속한 그룹(GRP_ID) 조회
3. 역할 확인: 그룹에 할당된 역할(ROLE_ID) 목록 조회
4. 메뉴 확인: 역할에 연결된 메뉴(MENU_ID) 목록 조회
5. URL 패턴 확인: 메뉴에 연결된 URL 패턴과 요청 URL 비교
6. 접근 허용/거부: 일치하는 URL 패턴이 있으면 접근 허용, 없으면 거부
5. **권한 관리의 장점**
이러한 계층적 권한 구조는 다음과 같은 이점을 제공합니다:
- **효율적인 권한 관리**: 그룹과 역할을 통해 다수의 사용자에게 일괄적으로 권한 부여 가능
- **세밀한 접근 제어**: URL 패턴 기반으로 세밀한 접근 제어 가능
- **유연한 권한 설계**: 사용자-그룹-역할-메뉴의 다단계 구조로 다양한 권한 정책 구현 가능
- **동적 메뉴 구성**: 사용자의 권한에 따라 UI 메뉴를 동적으로 구성 가능
- **관리 용이성**: 관리자 화면에서 그룹, 역할, 메뉴, 권한을 통합적으로 관리 가능
<h3 id="batch-management">5.7 배치 작업 관리</h3>
XIT Framework는 Quartz 기반의 배치 스케줄링 시스템을 내장하고 있습니다. 배치 작업은 등록, 실행, 일시정지, 재개, 삭제, 실행 이력 및 로그 관리 등 다양한 기능을 제공합니다.
### 5.7.1 전체 구조
- **Controller**: `BatchJobController` - 배치 작업의 웹/REST API 제공
- **Service**: `BatchJobService` - 배치 작업의 비즈니스 로직 처리
- **Job**: `SampleBatchJob`, `SampleBatchJob2`, `SampleBatchJob3` 등 - 실제 실행되는 배치 잡 구현체
- **Config**: `QuartzConfig`, `BatchJobInitializer`, `QuartzJobListener`, `QuartzListenerConfig` - 스케줄러 및 리스너 설정, 초기화
- **Mapper**: `BatchJobMapper` - 배치 작업/실행/로그 DB 연동
- **Model**: `BatchJobInfoVO`, `BatchJobExecutionVO`, `BatchJobLogVO` - 배치 정보/실행/로그 VO
- **Util**: `BatchJobLogUtil`, `ServerInfoUtil` - 배치 로그, 서버 정보 등 유틸리티
### 5.7.2 주요 기능 및 API
- **배치 작업 목록 조회**: `/batch/list.do`, `/batch/list.ajax`
- **배치 작업 즉시 실행**: `/batch/trigger.ajax` (POST)
- **배치 작업 일시정지/재개/삭제**: `/batch/pause.ajax`, `/batch/resume.ajax`, `/batch/delete.ajax` (POST)
- **실행 이력/로그 조회**: `/batch/execution.do`, `/batch/execution.ajax`, `/batch/log.do`, `/batch/log.ajax`
- **잡 등록/수정/삭제**: `/batch/register.ajax`
#### 예시: 배치 작업 즉시 실행
```http
POST /batch/trigger.ajax
Content-Type: application/x-www-form-urlencoded
jobId=...&jobName=SampleBatchJob&jobGroup=DEFAULT
```
### 5.7.3 배치 잡 예시
```java
@Component
@DisallowConcurrentExecution
public class SampleBatchJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 실제 배치 로직 구현
}
}
```
### 5.7.4 DB 연동 및 로그 관리
- **잡 정보/실행/로그**는 각각 TB_BATCH_JOB_INFO, TB_BATCH_JOB_EXECUTION, TB_BATCH_JOB_LOG 테이블에 저장
- `BatchJobMapper`를 통해 CRUD 및 이력/로그 관리
- `BatchJobLogUtil`로 실행 중 로그를 DB에 저장 가능
### 5.7.5 배치 스케줄러 및 리스너 설정
- `QuartzConfig`, `QuartzListenerConfig`에서 Quartz 스케줄러 및 JobListener 등록
- `BatchJobInitializer`에서 DB에 등록된 잡을 애플리케이션 시작 시 자동 등록
### 5.7.6 확장 및 커스터마이징
- 새로운 배치 잡은 `Job` 인터페이스 구현 후 등록
- 잡 등록/수정/삭제, 스케줄 변경 등은 Controller/Service/DB를 통해 관리
- 실행 이력, 평균 소요시간, 최근 실패 등 다양한 통계 제공
### 5.7.7 참고 VO 구조
- **BatchJobInfoVO**: 잡ID, 이름, 그룹, 클래스, 크론, 상태, 설명 등
- **BatchJobExecutionVO**: 실행ID, 잡이름/그룹, 시작/종료시간, 상태, 종료코드/메시지, 서버정보 등
- **BatchJobLogVO**: 로그ID, 실행ID, 로그레벨, 메시지, 시간 등
## 6. 개발 가이드라인
<h3 id="code-writing-rules">6.1 코드 작성 규칙</h3>
1. **Java 개발 및 수정**
- `src/main/java/go/kr/project/system` 하위에 있는 프로그램을 참조하여 구조, 패턴, 스타일을 사용합니다.
- 클래스, 메소드, 변수에 대한 주석을 작성합니다.
2. **JSP 개발 및 수정**
- `src/main/webapp/WEB-INF/views/system` 하위에 있는 프로그램을 참조하여 구조, 패턴, 스타일을 사용합니다.
- JSP 페이지에서 태그의 id 속성은 중복되지 않도록 합니다.
- 모든 URL은 `<c:url value="" />` 태그로 감싸줍니다.
3. **데이터베이스 관련**
- 신규로 생성되는 테이블은 `DB-DDL/maria/ddl.sql`에 등록합니다.
- `ON DELETE SET NULL`, `ON DELETE CASCADE` 등의 ON 구문은 사용하지 않고, 프로그램에서 직접 구현합니다.
4. **Ajax 처리**
- Ajax 요청 시 `src/main/java/egovframework/util/ApiResponseUtil.java``src/main/java/egovframework/util/ApiResponseEntity.java`를 참조하여 응답을 처리합니다.
- Ajax 응답을 처리할 때는 `response.success` 대신 `response.result`를 사용합니다.
<h3 id="directory-structure-guide">6.2 디렉토리 구조 가이드</h3>
1. **컨트롤러 클래스**
- `go.kr.project.{모듈명}.controller` 패키지에 위치합니다.
- 클래스명은 `{기능명}Controller.java` 형식으로 작성합니다.
2. **서비스 클래스**
- `go.kr.project.{모듈명}.service` 패키지에 위치합니다.
- 인터페이스는 `{기능명}Service.java`, 구현체는 `{기능명}ServiceImpl.java` 형식으로 작성합니다.
3. **매퍼 인터페이스**
- `go.kr.project.{모듈명}.mapper` 패키지에 위치합니다.
- 클래스명은 `{기능명}Mapper.java` 형식으로 작성합니다.
4. **모델 클래스**
- `go.kr.project.{모듈명}.model` 패키지에 위치합니다.
- 클래스명은 `{기능명}VO.java` 형식으로 작성합니다.
5. **JSP 파일**
- `src/main/webapp/WEB-INF/views/{모듈명}/{기능명}` 디렉토리에 위치합니다.
- 파일명은 `{화면명}.{레이아웃명}.jsp` 형식으로 작성합니다.
<h3 id="ui-component-guide">6.3 UI 컴포넌트 가이드</h3>
1. **TOAST UI Grid**
- 데이터 그리드를 표시할 때 TOAST UI Grid를 사용합니다.
- 그리드 초기화 및 설정은 `xit-tui-grid.js`를 참조합니다.
2. **폼 유효성 검사**
- 폼 유효성 검사는 `xit-validation.js`를 사용합니다.
3. **공통 스타일**
- 공통 스타일은 `common.css``xit-common.css`를 사용합니다.
## 7. 배포 가이드
<h3 id="build-method">7.1 빌드 방법</h3>
<h4 id="war-file-build">7.1.1 WAR 파일 빌드</h4>
```bash
# Gradle 빌드 (테스트 포함)
./gradlew clean build
# Gradle 빌드 (테스트 제외)
./gradlew clean build -x test
# WAR 파일만 생성
./gradlew war
```
<h4 id="bootwar-file-build">7.1.2 bootWar 파일 빌드</h4>
Spring Boot 애플리케이션은 실행 가능한 WAR 파일로 빌드할 수 있습니다.
```bash
# 실행 가능한 WAR 파일 생성
./gradlew bootWar
```
<h3 id="profile-settings">7.2 프로필 설정</h3>
XIT Framework는 다양한 환경에 맞게 설정할 수 있는 프로필 시스템을 제공합니다.
<h4 id="default-profile">7.2.1 기본 프로필</h4>
프로젝트는 다음과 같은 프로필을 제공합니다:
| 프로필 | 설명 | 주요 설정 |
|--------|------|-----------|
| local | 로컬 개발 환경 | 개발 도구 활성화, 로컬 경로 사용 |
| dev | 개발 서버 환경 | 개발 서버 설정, 세션 타임아웃 30분 |
| prd | 운영 서버 환경 | 운영 서버 설정, 리눅스 경로 사용 |
<h4 id="profile-activation">7.2.2 프로필 활성화 방법</h4>
<h5 id="application-property-settings">애플리케이션 속성 파일 설정</h5>
`application.yml` 파일에서 기본 프로필을 설정할 수 있습니다:
```yaml
spring:
profiles:
active: local # local, dev, prd 중 선택
```
<h5 id="command-line-profile-settings">명령행 인수로 프로필 설정</h5>
애플리케이션 실행 시 명령행 인수로 프로필을 지정할 수 있습니다:
```bash
# JAR 파일 실행 시
java -jar xit-framework.war --spring.profiles.active=dev
# Gradle로 실행 시
./gradlew bootRun --args='--spring.profiles.active=dev'
```
<h5 id="environment-variable-profile-settings">환경 변수로 프로필 설정</h5>
환경 변수를 통해 프로필을 설정할 수 있습니다:
```bash
# Windows
set SPRING_PROFILES_ACTIVE=dev
java -jar xit-framework.war
# Linux/macOS
export SPRING_PROFILES_ACTIVE=dev
java -jar xit-framework.war
```
<h3 id="external-was-deployment">7.3 외부 WAS를 이용한 배포</h3>
<h4 id="tomcat-war-deployment">7.3.1 Tomcat에 WAR 파일 배포</h4>
1. `build/libs` 디렉토리에 생성된 `xit-framework.war` 파일을 Tomcat의 `webapps` 디렉토리에 복사합니다.
2. Tomcat을 재시작합니다.
<h4 id="external-was-profile-settings">7.3.2 외부 WAS에서 프로필 설정</h4>
<h5 id="tomcat-profile-settings">Tomcat에서 프로필 설정</h5>
Tomcat의 `setenv.bat`(Windows) 또는 `setenv.sh`(Linux/macOS) 파일에 다음 내용을 추가합니다:
**Windows (setenv.bat)**:
```batch
set "JAVA_OPTS=%JAVA_OPTS% -Dspring.profiles.active=dev"
```
**Linux/macOS (setenv.sh)**:
```bash
export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=dev"
```
<h5 id="jvm-option-settings">WAS의 JVM 옵션으로 설정</h5>
대부분의 WAS에서는 JVM 옵션을 통해 시스템 속성을 설정할 수 있습니다:
```
-Dspring.profiles.active=dev
```
<h5 id="web-xml-settings">web.xml에 시스템 속성 설정</h5>
`web.xml` 파일에 다음과 같이 시스템 속성을 설정할 수 있습니다:
```xml
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>dev</param-value>
</context-param>
```
<h3 id="war-deployment-execution">7.4 WAR로 배포 및 실행</h3>
Spring Boot 애플리케이션은 bootWar로 빌드된 실행 가능한 WAR 파일로 배포할 수 있습니다.
<h4 id="bootwar-execution">7.4.1 bootWar 파일 실행</h4>
bootWar로 빌드된 WAR 파일은 내장 서버를 포함하고 있어 JAR 파일처럼 직접 실행할 수 있습니다:
```bash
# 기본 프로필로 실행
java -jar build/libs/xit-framework-boot.war
# 특정 프로필로 실행
java -jar build/libs/xit-framework-boot.war --spring.profiles.active=dev
# 포트 변경하여 실행
java -jar build/libs/xit-framework-boot.war --server.port=9090
# 메모리 설정을 추가하여 실행
java -Xms512m -Xmx1024m -jar build/libs/xit-framework-boot.war
# 로컬 테스트
java -jar build/libs/xit-framework-boot.war --spring.profiles.active=local --server.port=9090
```
<h4 id="external-was-execution">7.4.2 외부 WAS에 배포하여 실행</h4>
bootWar로 빌드된 WAR 파일은 외부 WAS(Tomcat, JBoss, WebLogic 등)에 배포하여 실행할 수도 있습니다:
1. `build/libs` 디렉토리에 생성된 `xit-framework.war` 파일을 WAS의 배포 디렉토리에 복사합니다.
- Tomcat: `webapps` 디렉토리
- JBoss/WildFly: `deployments` 디렉토리
- WebLogic: 관리 콘솔을 통해 배포
2. WAS를 시작하거나 재시작합니다.
<h4 id="background-execution">7.4.3 백그라운드 실행 (Linux/macOS)</h4>
내장 서버로 실행할 경우, 백그라운드에서 실행할 수 있습니다:
```bash
# nohup을 사용하여 백그라운드 실행
nohup java -jar build/libs/xit-framework.war > app.log 2>&1 &
# 실행 중인 프로세스 확인
ps -ef | grep java
```
<h4 id="windows-service-registration">7.4.4 Windows 서비스로 등록</h4>
Windows에서는 [winsw](https://github.com/winsw/winsw)와 같은 도구를 사용하여 Spring Boot 애플리케이션을 Windows 서비스로 등록할 수 있습니다.
<h3 id="deployment-environment-settings">7.5 배포 환경 설정</h3>
1. **Java 버전**: 배포 환경에서는 Java 1.8을 사용합니다.
2. **서버**:
- 내장 서버(bootWar 직접 실행): 내장된 Tomcat 9.0.78 사용
- 외부 서버(bootWar 배포): Tomcat 9.0.78 이상 권장
3. **데이터베이스**: MariaDB를 사용합니다.
4. **메모리 설정**: 애플리케이션 크기와 사용자 수에 따라 적절한 JVM 메모리 설정이 필요합니다.
<h3 id="deployment-checklist">7.6 배포 체크리스트</h3>
1. **프로필 설정 확인**: 배포 환경에 맞는 프로필이 활성화되었는지 확인합니다.
2. **데이터베이스 연결 확인**: 데이터베이스 연결 정보가 올바르게 설정되었는지 확인합니다.
3. **파일 업로드 경로 확인**: 파일 업로드 경로가 존재하고 쓰기 권한이 있는지 확인합니다.
4. **로그 경로 확인**: 로그 파일 경로가 존재하고 쓰기 권한이 있는지 확인합니다.
5. **메모리 설정 확인**: JVM 메모리 설정이 적절한지 확인합니다.
6. **포트 충돌 확인**: 사용할 포트가 다른 애플리케이션과 충돌하지 않는지 확인합니다.
## 8. 참고 자료
- [Spring Boot 공식 문서](https://docs.spring.io/spring-boot/docs/2.7.18/reference/html/)
- [전자정부 프레임워크 가이드](https://www.egovframe.go.kr/wiki/doku.php)
- [MyBatis 공식 문서](https://mybatis.org/mybatis-3/ko/index.html)
- [TOAST UI Grid 공식 문서](https://ui.toast.com/tui-grid)
- [TOAST UI Editor 공식 문서](https://ui.toast.com/tui-editor)