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

58 KiB

XIT Framework 개발 가이드

목차

1. 프로젝트 개요

XIT Framework는 Spring Boot 기반의 웹 애플리케이션 프레임워크로, 전자정부 프레임워크를 확장하여 개발된 프로젝트입니다. 이 문서는 XIT Framework의 구조, 기술 스택, 주요 기능 및 사용법에 대한 종합적인 가이드를 제공합니다.

2. 기술 스택 및 라이브러리 버전

2.1 핵심 기술 스택

기술 버전 설명
Java 개발: 1.8, 배포: 1.8 자바 개발 및 실행 환경
Spring Boot 2.7.18 스프링 기반 애플리케이션 개발 프레임워크
전자정부 프레임워크 4.3.0 한국 정부 표준 웹 개발 프레임워크
Servlet 3.1 웹 애플리케이션 표준
Gradle - 빌드 및 의존성 관리 도구
MariaDB - 관계형 데이터베이스

2.2 주요 라이브러리

라이브러리 버전 설명
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. 프로젝트 구조

3.1 디렉토리 구조

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 빌드 스크립트

3.2 패키지 구조

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. 주요 설정 파일

4.1 application.yml

애플리케이션의 공통 설정을 정의하는 파일입니다.

# 공통 설정
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/**

4.2 application-local.yml

로컬 개발 환경에 대한 설정을 정의하는 파일입니다.

# 로컬 프로필 설정
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

4.3 application-dev.yml

개발 환경에 대한 설정을 정의하는 파일입니다.

# 개발 프로필 설정
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

4.4 application-prd.yml

운영 환경에 대한 설정을 정의하는 파일입니다.

# 운영 프로필 설정
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

4.5 build.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'
}

4.6 logback-spring.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>

4.7 mybatis-config.xml

MyBatis 설정을 정의하는 파일입니다.

<?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>

4.8 데이터베이스 스키마

주요 테이블 구조는 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. 주요 기능 및 사용법

5.1 프로젝트 구조 패턴

XIT Framework는 MVC(Model-View-Controller) 패턴을 기반으로 하며, 각 기능별로 다음과 같은 계층 구조를 가집니다:

  1. Controller: 클라이언트 요청을 처리하고 응답을 반환합니다.
  2. Service: 비즈니스 로직을 처리합니다.
  3. Mapper: 데이터베이스 접근을 담당합니다.
  4. Model: 데이터 구조를 정의합니다.

5.2 사용자 관리 기능 예시

사용자 관리 기능은 다음과 같은 구조로 구현되어 있습니다:

5.2.1 Controller

@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("사용자 등록에 실패했습니다.");
        }
    }
}

5.2.2 Service

@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);
}

5.2.3 Mapper

@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);
}

5.3 로그인 기능

로그인 기능은 다음과 같은 구조로 구현되어 있습니다:

5.3.1 Controller

@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();
    }
}

5.4 공통 유틸리티

5.4.1 API 응답 유틸리티

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);
    }
}

5.5 레이아웃 구성

XIT Framework는 Apache Tiles를 사용하여 레이아웃을 구성합니다. 기본 레이아웃은 src/main/webapp/WEB-INF/views/layouts/base/default.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 헤더를 설정하여 항상 최신 콘텐츠를 제공합니다.

5.6 보안 기능

XIT Framework는 다양한 보안 기능을 제공하여 애플리케이션의 안전성을 강화합니다.

5.6.1 XSS 필터

XSS(Cross-Site Scripting) 공격을 방지하기 위한 필터를 제공합니다. 이 필터는 모든 요청 파라미터를 검사하고 잠재적인 XSS 공격 코드를 제거합니다.

// 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 헤더를 설정합니다.
// 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);
    }
}

5.6.2 리퍼러 체크

리퍼러(Referer) 헤더를 검사하여 직접 URL을 입력하거나 외부 사이트에서의 접근을 제한합니다. 이를 통해 CSRF(Cross-Site Request Forgery) 공격을 방지하고 애플리케이션의 보안을 강화합니다.

// 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. 일반 요청 처리: 일반 요청의 경우 경고 메시지와 함께 로그인 페이지로 리다이렉트합니다.

5.6.3 권한 관리

사용자의 권한에 따라 접근 가능한 기능과 메뉴를 제한하는 권한 관리 시스템을 제공합니다. 권한 관리는 인터셉터를 통해 구현되며, 사용자의 세션 정보와 요청 URL을 기반으로 접근 권한을 검사합니다.

// 권한 관리 인터셉터
@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;
    }
}

5.6.4 사용자 > 그룹 > 역할 > 메뉴 구조

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 메뉴를 동적으로 구성 가능
    • 관리 용이성: 관리자 화면에서 그룹, 역할, 메뉴, 권한을 통합적으로 관리 가능

5.7 배치 작업 관리

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

예시: 배치 작업 즉시 실행

POST /batch/trigger.ajax
Content-Type: application/x-www-form-urlencoded

jobId=...&jobName=SampleBatchJob&jobGroup=DEFAULT

5.7.3 배치 잡 예시

@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. 개발 가이드라인

6.1 코드 작성 규칙

  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.javasrc/main/java/egovframework/util/ApiResponseEntity.java를 참조하여 응답을 처리합니다.
    • Ajax 응답을 처리할 때는 response.success 대신 response.result를 사용합니다.

6.2 디렉토리 구조 가이드

  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 형식으로 작성합니다.

6.3 UI 컴포넌트 가이드

  1. TOAST UI Grid

    • 데이터 그리드를 표시할 때 TOAST UI Grid를 사용합니다.
    • 그리드 초기화 및 설정은 xit-tui-grid.js를 참조합니다.
  2. 폼 유효성 검사

    • 폼 유효성 검사는 xit-validation.js를 사용합니다.
  3. 공통 스타일

    • 공통 스타일은 common.cssxit-common.css를 사용합니다.

7. 배포 가이드

7.1 빌드 방법

7.1.1 WAR 파일 빌드

# Gradle 빌드 (테스트 포함)
./gradlew clean build

# Gradle 빌드 (테스트 제외)
./gradlew clean build -x test

# WAR 파일만 생성
./gradlew war

7.1.2 bootWar 파일 빌드

Spring Boot 애플리케이션은 실행 가능한 WAR 파일로 빌드할 수 있습니다.

# 실행 가능한 WAR 파일 생성
./gradlew bootWar

7.2 프로필 설정

XIT Framework는 다양한 환경에 맞게 설정할 수 있는 프로필 시스템을 제공합니다.

7.2.1 기본 프로필

프로젝트는 다음과 같은 프로필을 제공합니다:

프로필 설명 주요 설정
local 로컬 개발 환경 개발 도구 활성화, 로컬 경로 사용
dev 개발 서버 환경 개발 서버 설정, 세션 타임아웃 30분
prd 운영 서버 환경 운영 서버 설정, 리눅스 경로 사용

7.2.2 프로필 활성화 방법

애플리케이션 속성 파일 설정

application.yml 파일에서 기본 프로필을 설정할 수 있습니다:

spring:
  profiles:
    active: local  # local, dev, prd 중 선택
명령행 인수로 프로필 설정

애플리케이션 실행 시 명령행 인수로 프로필을 지정할 수 있습니다:

# JAR 파일 실행 시
java -jar xit-framework.war --spring.profiles.active=dev

# Gradle로 실행 시
./gradlew bootRun --args='--spring.profiles.active=dev'
환경 변수로 프로필 설정

환경 변수를 통해 프로필을 설정할 수 있습니다:

# Windows
set SPRING_PROFILES_ACTIVE=dev
java -jar xit-framework.war

# Linux/macOS
export SPRING_PROFILES_ACTIVE=dev
java -jar xit-framework.war

7.3 외부 WAS를 이용한 배포

7.3.1 Tomcat에 WAR 파일 배포

  1. build/libs 디렉토리에 생성된 xit-framework.war 파일을 Tomcat의 webapps 디렉토리에 복사합니다.
  2. Tomcat을 재시작합니다.

7.3.2 외부 WAS에서 프로필 설정

Tomcat에서 프로필 설정

Tomcat의 setenv.bat(Windows) 또는 setenv.sh(Linux/macOS) 파일에 다음 내용을 추가합니다:

Windows (setenv.bat):

set "JAVA_OPTS=%JAVA_OPTS% -Dspring.profiles.active=dev"

Linux/macOS (setenv.sh):

export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=dev"
WAS의 JVM 옵션으로 설정

대부분의 WAS에서는 JVM 옵션을 통해 시스템 속성을 설정할 수 있습니다:

-Dspring.profiles.active=dev
web.xml에 시스템 속성 설정

web.xml 파일에 다음과 같이 시스템 속성을 설정할 수 있습니다:

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>

7.4 WAR로 배포 및 실행

Spring Boot 애플리케이션은 bootWar로 빌드된 실행 가능한 WAR 파일로 배포할 수 있습니다.

7.4.1 bootWar 파일 실행

bootWar로 빌드된 WAR 파일은 내장 서버를 포함하고 있어 JAR 파일처럼 직접 실행할 수 있습니다:

# 기본 프로필로 실행
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

7.4.2 외부 WAS에 배포하여 실행

bootWar로 빌드된 WAR 파일은 외부 WAS(Tomcat, JBoss, WebLogic 등)에 배포하여 실행할 수도 있습니다:

  1. build/libs 디렉토리에 생성된 xit-framework.war 파일을 WAS의 배포 디렉토리에 복사합니다.

    • Tomcat: webapps 디렉토리
    • JBoss/WildFly: deployments 디렉토리
    • WebLogic: 관리 콘솔을 통해 배포
  2. WAS를 시작하거나 재시작합니다.

7.4.3 백그라운드 실행 (Linux/macOS)

내장 서버로 실행할 경우, 백그라운드에서 실행할 수 있습니다:

# nohup을 사용하여 백그라운드 실행
nohup java -jar build/libs/xit-framework.war > app.log 2>&1 &

# 실행 중인 프로세스 확인
ps -ef | grep java

7.4.4 Windows 서비스로 등록

Windows에서는 winsw와 같은 도구를 사용하여 Spring Boot 애플리케이션을 Windows 서비스로 등록할 수 있습니다.

7.5 배포 환경 설정

  1. Java 버전: 배포 환경에서는 Java 1.8을 사용합니다.
  2. 서버:
    • 내장 서버(bootWar 직접 실행): 내장된 Tomcat 9.0.78 사용
    • 외부 서버(bootWar 배포): Tomcat 9.0.78 이상 권장
  3. 데이터베이스: MariaDB를 사용합니다.
  4. 메모리 설정: 애플리케이션 크기와 사용자 수에 따라 적절한 JVM 메모리 설정이 필요합니다.

7.6 배포 체크리스트

  1. 프로필 설정 확인: 배포 환경에 맞는 프로필이 활성화되었는지 확인합니다.
  2. 데이터베이스 연결 확인: 데이터베이스 연결 정보가 올바르게 설정되었는지 확인합니다.
  3. 파일 업로드 경로 확인: 파일 업로드 경로가 존재하고 쓰기 권한이 있는지 확인합니다.
  4. 로그 경로 확인: 로그 파일 경로가 존재하고 쓰기 권한이 있는지 확인합니다.
  5. 메모리 설정 확인: JVM 메모리 설정이 적절한지 확인합니다.
  6. 포트 충돌 확인: 사용할 포트가 다른 애플리케이션과 충돌하지 않는지 확인합니다.

8. 참고 자료