# Developer Platform

Welcome to your team’s developer platform

<h2 align="center">Develop with Document!</h2>

<p align="center">X2BEE의 원활한 프로젝트 수행을 위한 개발 및 API 가이드를 한 곳에 정리했습니다. <br>필수 가이드를 빠르게 확인하고, 안정적인 개발을 시작하세요!</p>

### X2BEE 가이드

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><h4><i class="fa-leaf">:leaf:</i></h4></td><td><strong>프로젝트 준비하기</strong></td><td><p>성공적인 프로젝트 수행을 위한 </p><p>X2BEE 솔루션에 대한 주요 정보를 </p><p>알아보세요</p></td><td><a href="/spaces/VEZx3rsZsIv89GPS3d2J/pages/PbYb0GukRhiS4qCHdRal">/spaces/VEZx3rsZsIv89GPS3d2J/pages/PbYb0GukRhiS4qCHdRal</a></td><td><a href="/files/LHIOZGoI6dPF36TuRaxH">/files/LHIOZGoI6dPF36TuRaxH</a></td></tr><tr><td><h4><i class="fa-server">:server:</i></h4></td><td><strong>개발 시작하기</strong></td><td><p>X2BEE 개발 환경 설정을 시작으로 </p><p>실제 개발 업무에 필요한 중요한 정보를</p><p>파악하세요</p></td><td><a href="/spaces/VEZx3rsZsIv89GPS3d2J/pages/aOjs4gC5WzIpifbjuGsn">/spaces/VEZx3rsZsIv89GPS3d2J/pages/aOjs4gC5WzIpifbjuGsn</a></td><td><a href="/files/IaUMODRBBSBL8eUaPX09">/files/IaUMODRBBSBL8eUaPX09</a></td></tr><tr><td><h4><i class="fa-terminal">:terminal:</i></h4></td><td><strong>API 알아보기</strong></td><td><p>빠른 기능개발과 활용을 위한 </p><p>Store Front API와 Back Office API를</p><p>자세히 확인해보세요</p></td><td><a href="/spaces/XTxKSWMmuxEkop1pvuwe">/spaces/XTxKSWMmuxEkop1pvuwe</a></td><td><a href="/files/ebMoLMpZFyLqpzByuzrk">/files/ebMoLMpZFyLqpzByuzrk</a></td></tr></tbody></table>

{% columns %}
{% column %}

### API 레퍼런스

코드 구현이나 환경 설정 없이도 원하는 API를 실행해보세요

명확한 엔드포인트 안내, 복사해 바로 사용할 수 있는 예제 코드, 빠른 인증 절차 등을 통해 몇 시간이 아닌 몇 분안에 실행할 수 있습니다.<br>

<a href="/spaces/g6Ns2LkjvibDmBGVE6eQ/pages/trLwwmumlaofzMKwR3fr" class="button primary" data-icon="globe-pointer">X2BEE API 테스트하기</a>
{% endcolumn %}

{% column %}
{% code title="index.js" overflow="wrap" %}

```javascript
// Import the SDK
import ExampleAPI from "example-api";

// Initialize the client
const client = new ExampleAPI({ apiKey: "YOUR_API_KEY" });

// Send your first message
const response = await client.messages.send({
  message: "Hello, world!"
});

```

{% endcode %}
{% endcolumn %}
{% endcolumns %}

{% columns %}
{% column width="33.33333333333333%" %}

<figure><img src="/files/E0J1fPQj6mGRNk481LIn" alt=""><figcaption></figcaption></figure>
{% endcolumn %}

{% column width="66.66666666666667%" %}

### 신규 업데이트

릴리즈 노트에서는 X2BEE의 최신 업데이트 내역을 확인할 수 있습니다.

기능 추가, 개선 사항, 변경 및 수정 내용 등을 간단히 정리하여 제공합니다.<br>

<a href="/spaces/K3Y7JrvxsOuMU7L5r2ra/pages/xSKACgaFjsHdWkvrL7EZ" class="button primary" data-icon="file-lines">릴리즈 노트</a>
{% endcolumn %}
{% endcolumns %}


# 개발 가이드

**개발 가이드**는 프로젝트의 설계부터 구현, 운영까지 전 과정을 하나의 기준 아래 정리한 통합 가이드입니다.<br>

팀이 동일한 방향과 원칙을 공유할 수 있도록 표준을 제시하고, 실제 개발 과정에서는 바로 적용할 수 있는 실행 중심의 가이드를 제공합니다.

이를 통해 불필요한 시행착오를 줄이고, 빠른 온보딩과 안정적인 품질 확보를 지원합니다.\
결과적으로 개발 효율성과 협업 일관성을 동시에 높이는 것을 목표로 합니다.

### :mag\_right:X2BEE 프로젝트 시작하기

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><h4><strong>프로젝트 준비하기</strong></h4></td><td><p>프로젝트 시작에 앞서, 효율적인 개발을 위해</p><p>X2BEE 솔루션에 대해 알아야 할 필수정보를 </p><p>설명합니다</p></td><td><a href="/files/XmHJphQkfnNhshSECN41">/files/XmHJphQkfnNhshSECN41</a></td><td></td><td><a href="/pages/PbYb0GukRhiS4qCHdRal">/pages/PbYb0GukRhiS4qCHdRal</a></td></tr><tr><td><h4><strong>개발 시작하기</strong></h4></td><td><p>개발 환경 설정을 시작으로 일관성있는 높은 </p><p>품질의 코드 개발을 위해 필요한 중요 정보를 </p><p>설명합니다</p></td><td><a href="/files/lTJHYwlbx4xYiy7dSbrc">/files/lTJHYwlbx4xYiy7dSbrc</a></td><td></td><td><a href="/pages/aOjs4gC5WzIpifbjuGsn">/pages/aOjs4gC5WzIpifbjuGsn</a></td></tr></tbody></table>


# 프로젝트 구조 표준 정의서

다양한 X2BEE 프로젝트와 하위 패키지들의 구조를 파악하는 데 필요한 핵심 정보를 제공합니다.

프로젝트 개발이 보다 효율적으로 작업될 수 있도록 지원하기 위해 각 프로젝트와 패키지 명칭과 설명 그리고 구조에 대해 상세히 설명하고 있습니다.

***

## Application Service 구성도 <a href="#application-service" id="application-service"></a>

<figure><img src="/files/4wGrwqHU9j4soxHuIaew" alt=""><figcaption></figcaption></figure>

## 프로젝트 명칭 및 설명 <a href="#undefined" id="undefined"></a>

<table><thead><tr><th width="203">프로젝트명</th><th>설명</th></tr></thead><tbody><tr><td>x2bee-fo</td><td>Store Front 프로그램 프로젝트(Mobile, PC 구분없이 반응형으로 개발)</td></tr><tr><td>x2bee-gw</td><td>Store Front 프로그램 api gateway</td></tr><tr><td>x2bee-api-member</td><td>고객 프로그램 api 프로젝트</td></tr><tr><td>x2bee-api-order</td><td>주문 프로그램 api 프로젝트</td></tr><tr><td>x2bee-api-goods</td><td>상품 프로그램 api 프로젝트</td></tr><tr><td>x2bee-api-display</td><td>전시 프로그램 api 프로젝트</td></tr><tr><td>x2bee-api-event</td><td>이벤트 프로그램 api 프로젝트</td></tr><tr><td>x2bee-api-bo</td><td>관리자/고객센터 프로그램 용 api 프로젝트</td></tr><tr><td>x2bee-api-common</td><td>공통 프로그램 api 프로젝트</td></tr><tr><td>x2bee-bo</td><td>관리자 BackOffice Front 프로젝트(Mobile, PC 구분없이 반응형으로 개발)</td></tr><tr><td>x2bee-batch-mbod</td><td>회원/주문 배치 프로그램 프로젝트</td></tr><tr><td>x2bee-batch-gddp</td><td>상품/전시 배치 프로그램 프로젝트</td></tr><tr><td>x2bee-common</td><td><p>공통클래스 프로젝트</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>이 프로젝트는 기본 클래스와 공용 유틸리티 클래스 등을 포함하며, 엔터티/DTO 클래스는 각각의 개별 프로젝트에서 관리됩니다.</p></div></td></tr><tr><td>x2bee-api-search</td><td>검색엔진 프로젝트</td></tr></tbody></table>

## 프로젝트 패키지 구조 <a href="#undefined" id="undefined"></a>

### x2bee-common <a href="#x2bee-common" id="x2bee-common"></a>

* 프레임워크 공통 클래스 패키지 : 프로젝트 공통 클래스, 유틸 클래스, 커스텀 어노테이션, 예외처리, DB관련 속성, 보안 등 포함
* API 관련 비즈니스 로직 클래스 패키지 : Controller, Service, Dao, Entity, repository 클래스
* 프레임워크 관련 로직 클래스 패키지 : advice, aop, config, security 클래스
* 설정 관련 로직 클래스 패키지 : X2beeApiApplication, pom.xml 등 프로젝트 관련 설정 클래스

| /src/main/java    | 자바 소스                             |                               |
| ----------------- | --------------------------------- | ----------------------------- |
|                   | com.x2bee.common                  | 패키지 네임스페이스                    |
|                   | base                              | base                          |
|                   | advice                            | global responseBodyAdvice 클래스 |
| annotation        | 어노테이션 정의                          |                               |
| aop               | 사이트, mybatis, db 설정 aop           |                               |
| bizmessages       | ncp message 전송 클래스                |                               |
| constant          | 상수정의                              |                               |
| context           | Spring application context 편의 클래스 |                               |
| encrypt           | 암복호화 처리 클래스                       |                               |
| entity            | 공통 엔티티 Bean                       |                               |
| exception         | exception 정의                      |                               |
| filter            | servlet request filter 클래스        |                               |
| formatter         | datatype format Convert 클래스       |                               |
| interceptor       | Controller Interceptor 클래스        |                               |
| mail              | mail 전송 클래스                       |                               |
| masking           | masking 처리 관련 클래스                 |                               |
| messageconverter  | json message convert 처리 클래스       |                               |
| mybatis           | mybatis 클래스                       |                               |
| properties        | properties 처리 클래스                 |                               |
| propertyeditor    | Grid String to Timestamp 클래스      |                               |
| redis             | redis 클래스                         |                               |
| rest              | rest api 호출을 위한 편의 클래스            |                               |
| routingdatasource | routingDataSource 클래스             |                               |
| service           | 불용어 처리 클래스                        |                               |
| token             | 인증토큰 처리 클래스                       |                               |
| tree              | list를 tree 변환 클래스                 |                               |
| upload            | 파일 업로드 클래스                        |                               |
| util              | 편의 클래스 정의                         |                               |
| xss               | xss 처리 클래스                        |                               |

&#x20;

### x2bee-api.{msa명}  <a href="#x2bee-api.-msa" id="x2bee-api.-msa"></a>

* java 폴더 구조

| /src/main/java | 자바 소스                              |                              |
| -------------- | ---------------------------------- | ---------------------------- |
|                | com.x2bee.api.{msa}                | 패키지 네임스페이스                   |
|                | base                               | 공통                           |
|                | advice                             | Spring controller advice 클래스 |
| aop            | 서비스 공통 aspect 설정 클래스               |                              |
| config         | Spring config bean, security 설정    |                              |
| filter         | Spring Filter 클래스                  |                              |
| utils          | 편의 클래스 정의                          |                              |
| app            | 어플리케이션 클래스                         |                              |
|                | controller                         | @Controller                  |
|                | 모듈명                                | 각 모듈 별 @Controller           |
| service        | @Service                           |                              |
|                | 모듈명                                | 각 모듈 별 @Service              |
| repository     | @Repository (\* Mybatis Mapper 이용) |                              |
|                | {dbname}rodb                       | Read Only DB Repository      |
|                | 모듈명                                | 각 모듈 별 @Repository           |
| {dbname}rwdb   | Read Write Repository              |                              |
|                | 모듈명                                | 각 모듈 별 @Repository           |
| dto            | 파라미터/응답데이터/DB조회데이터용 dto 정의         |                              |
|                | request                            | 요청 dto 정의                    |
|                | 모듈명                                | 각 모듈 별 dto                   |
| response       | 응답 dto 정의                          |                              |
|                | 모듈명                                | 각 모듈 별 dto                   |
| entity         | DB 등록/수정/삭제용 entity Bean 정의        |                              |
| constant       | 상수정의                               |                              |
| enums          | enum 파일                            |                              |

* resource 폴더 구조

| /src/main/resource |                                    |                                    |
| ------------------ | ---------------------------------- | ---------------------------------- |
|                    | config                             | {MSA} 설정 파일                        |
| mapper             | Mybatis Mapper (.xml) 파일           |                                    |
|                    | {dbname}rodb                       | Read Only (Replica) Database 쿼리 파일 |
|                    | 모듈명                                | 모듈별 mapper 파일                      |
| {dbname}rwdb       | Read Write (Master) Database 쿼리 파일 |                                    |
|                    | 모듈명                                | 모듈별 mapper 파일                      |
| templates          |                                    |                                    |
|                    | email                              | Email Html 템플릿 파일(Api Common 프로젝트) |
| message            | 다국어 처리 메시지 정의 파일                   |                                    |
|                    | 모듈명                                | 모듈별 message 파일                     |

### x2bee-api-bo  <a href="#x2bee-api-bo" id="x2bee-api-bo"></a>

* java 폴더 구조

| /src/main/java | 자바 소스                              |                               |
| -------------- | ---------------------------------- | ----------------------------- |
|                | com.x2bee.bo.api                   | 패키지 네임스페이스                    |
|                | base                               | 공통                            |
|                | advice                             | Spring controller advice 클래스  |
| annotation     | 어노테이션 정의                           |                               |
| aop            | 서비스 공통 aspect 설정 클래스               |                               |
| config         | Spring config bean, security 설정    |                               |
| decorator      | TaskDecorator 클래스                  |                               |
| filter         | Spring Filter 클래스                  |                               |
| interceptor    | Controller Interceptor 클래스         |                               |
| masking        | masking 처리 관련 클래스                  |                               |
| properties     | properties 처리 클래스                  |                               |
| repository     | 공통 Code Repository                 |                               |
| util           | 편의 클래스 정의                          |                               |
| app            | 어플리케이션 클래스                         |                               |
|                | controller                         | @Controller                   |
|                | 모듈명                                | 각 모듈 별 @Controller            |
| service        | @Service                           |                               |
|                | 모듈명                                | 각 모듈 별 @Service               |
| repository     | @Repository (\* Mybatis Mapper 이용) |                               |
|                | orderrodb                          | 회원/주문 DB Read Only Repository |
|                | 모듈명                                | 각 모듈 별 @Repository            |
| orderrwdb      | 회원/주문 DB Read Write Repository     |                               |
|                | 모듈명                                | 각 모듈 별 @Repository            |
| displayrodb    | 상품/전시 DB Read Only Repository      |                               |
|                | 모듈명                                | 각 모듈 별 @Repository            |
| displayrwdb    | 상품/전시 DB Read Write Repository     |                               |
|                | 모듈명                                | 각 모듈 별 @Repository            |
| eventrodb      | 이벤트 DB Read Only Repository        |                               |
|                | 모듈명                                | 각 모듈 별 @Repository            |
| eventrwdb      | 이벤트 DB Read Write Repository       |                               |
|                | 모듈명                                | 각 모듈 별 @Repository            |
| dto            | 요청파라미터/응답데이터/DB조회데이터용 dto 정의       |                               |
|                | request                            | 요청 dto 정의                     |
|                | 모듈명                                | 각 모듈 별 dto                    |
| response       | 응답 dto 정의                          |                               |
|                | 모듈명                                | 각 모듈 별 dto                    |
| entity         | DB 등록/수정/삭제용 entity Bean 정의        |                               |
| constant       | 상수정의                               |                               |
| enums          | enum 파일                            |                               |

* resource 폴더 구조

| /src/main/resource                                                               |                                          |                                    |
| -------------------------------------------------------------------------------- | ---------------------------------------- | ---------------------------------- |
|                                                                                  | config                                   | 설정 파일                              |
| mapper                                                                           | Mybatis Mapper (.xml) 파일                 |                                    |
| <p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p> | orderrodb                                | 회원/주문 DB Read Only (Replica) 쿼리 파일 |
|                                                                                  | 모듈명                                      | 모듈별 mapper 파일                      |
| orderrwdb                                                                        | 회원/주문 Read Write (Master) Database 쿼리 파일 |                                    |
|                                                                                  | 모듈명                                      | 모듈별 mapper 파일                      |
| displayrodb                                                                      | 상품/전시 DB Read Only (Replica) 쿼리 파일       |                                    |
|                                                                                  | 모듈별 mapper 파일                            | 모듈별 mapper 파일                      |
| displayrwdb                                                                      | 상품/전시 DB Read Write (Master) 쿼리 파일       |                                    |
|                                                                                  | 모듈별 mapper 파일                            | 모듈별 mapper 파일                      |
| eventrodb                                                                        | 이벤트 DB Read Only (Replica) 쿼리 파일         |                                    |
|                                                                                  | 모듈별 mapper 파일                            | 모듈별 mapper 파일                      |
| eventrwdb                                                                        | 이벤트 DB Read Write (Master) 쿼리 파일         |                                    |
|                                                                                  | 모듈별 mapper 파일                            | 모듈별 mapper 파일                      |
| message                                                                          | 다국어 처리 메시지 정의 파일                         |                                    |
|                                                                                  | 모듈명                                      | 모듈별 message 파일                     |

&#x20;

### x2bee-batch-mbod/x2bee-batch-gddp  <a href="#x2bee-batch-mbod-x2bee-batch-gddp" id="x2bee-batch-mbod-x2bee-batch-gddp"></a>

* java 폴더 구조

| /src/main/java | 자바 소스                              |                         |
| -------------- | ---------------------------------- | ----------------------- |
|                | com.x2bee.batch                    | 패키지 네임스페이스              |
|                | base                               | 공통                      |
|                | aop                                | 서비스 공통 aspect 설정 클래스    |
| config         | Spring config bean 설정              |                         |
| context        | Context Holder 클래스                 |                         |
| listener       | Batch listener 설                   |                         |
| redismessage   | redis 설                            |                         |
| util           | 편의 클래스 정의                          |                         |
| app            | 어플리케이션 클래스                         |                         |
|                | controller                         | @Controller             |
|                | 모듈명                                | 각 모듈 별 @Controller      |
| jobconfig      | 배치잡 정의                             |                         |
|                | 각 모듈 별 배치잡                         | 각 모듈 별 배치잡              |
| service        | @Service                           |                         |
|                | 모듈명                                | 각 모듈 별 @Service         |
| repository     | @Repository (\* Mybatis Mapper 이용) |                         |
|                | {dbname}rodb                       | Read Only DB Repository |
|                | 모듈명                                | 각 모듈 별 @Repository      |
| {dbname}rwdb   | Read Write Repository              |                         |
|                | 모듈명                                | 각 모듈 별 @Repository      |
| 모듈명            | 각 모듈 별 @Repository                 |                         |
| dto            | DB 조회 데이터용 dto 정의                  |                         |
|                | 모듈명                                | 각 모듈 별 dto 정의           |
| entity         | DB 등록/수정/삭제용 entity Bean 정의        |                         |
| constant       | 상수정의                               |                         |
| enums          | enum 파일                            |                         |

&#x20;

* resource 폴더 구조

| /src/main/resource |                                    |                              |
| ------------------ | ---------------------------------- | ---------------------------- |
|                    | config                             | 설정 파일                        |
| Mapper             | Mybatis Mapper (.xml) 파일           |                              |
|                    | {dbname}rodb                       | DB Read Only (Replica) 쿼리 파일 |
|                    | 모듈명                                | 모듈별 mapper 파일                |
| {dbname}rwdb       | Read Write (Master) Database 쿼리 파일 |                              |
|                    | 모듈명                                | 모듈별 mapper 파일                |
| message            | 다국어 처리 메시지 정의 파일                   |                              |
|                    | 모듈명                                | 모듈별 message 파일               |

&#x20;

<br>


# 프로젝트 개발 및 표준 가이드

## 개요

X2BEE-API 프로젝트 개발, X2BEE-BO 프로젝트 개발, 그리고 X2BEE-Batch 프로젝트 개발에 대한 상세 내용을 다루고 있습니다. 더불어, 프로젝트 개발 시 필요한 일관성과 효율성을 유지하기 위한 프로그래밍 일반 표준 가이드라인과 규칙을 제시하고 있습니다.

***

## 문서 구성

<details>

<summary><a href="/pages/4afb7884d0c1c3dee60a4e2ae0a978e83060fb99"><strong>프로그래밍 일반 표준</strong></a></summary>

프로그래밍 일반 표준에 따른 코딩 규칙과 관례를 설명합니다

</details>

<details>

<summary><a href="/pages/1c9009ffc0b35a5bf460bbda905b91269d44d18f"><strong>API 개발 가이드</strong></a></summary>

X2BEE-API 프로젝트 개발과 관련된 정보와 지침을 설명합니다

</details>

<details>

<summary><a href="/pages/ad43a6004ddbed2891ed4ab93244575fc86e907a"><strong>BO 개발 가이드</strong></a></summary>

X2BEE-BO 프로젝트 개발과 관련된 정보와 지침을 설명합니다

</details>

<details>

<summary><a href="/pages/817c0f62ee4cfe43c8123db114f1dd52b20e1bd8"><strong>Batch 개발 가이드</strong></a></summary>

X2BEE-Batch 개발에 필요한 지침과 실제 예시를 설명합니다

</details>

<details>

<summary><a href="/pages/Ps7eop4Lb8qcBR9V7aXA"><strong>Store Front Framework 개발 가이드</strong></a></summary>

X2BEE- Store Front 개발과 관련된 정보와 지침을 설명합니다

</details>

<details>

<summary><a href="/pages/QPzbTvC6XsT5gERiU43E#undefined-3"><strong>약어집</strong></a></summary>

X2BEE에서 사용되는 약어집 정보를 설명합니다

</details>


# 프로그래밍 일반표준

X2BEE NEXT프로젝트에서 사용되는 프로그래밍 일반 표준에 대해 설명합니다. 이 가이드라인을 따르면 일관된 코드 스타일을 유지하며 프로젝트의 효율성과 가독성을 높일 수 있습니다.

***

## 명명규칙

1. **약어집 활용**
   * 모든 명명은 [약어집](/dev-guide/pjt-prepare/publish-your-docs#undefined-3)을 활용해 작성합니다.
   * 모듈 구분 폴더명은 약어집 참고 명명규칙에서 제외합니다. (예: display, goods, event, order 등)

2. **URL 생성 정책**

   app 라우터를 사용하며, 모듈명/상위 메뉴명/화면기능명 형태로 생성합니다.

   * **모듈명**: 풀네임 사용
   * **상위 메뉴명**: 15자 기준으로 최대한 맞추어 약어 사용하여 조합
   * **화면 기능명**: 20자 기준으로 최대한 맞추어 약어 사용하여 조합
   * 그 외 일반적인 단어는 약어로 처리합니다 (예: mgmt, info, reg, mod)

   예시:

   * /goods/goodsMgmt.goodsMgmtView\.do → src/app/\[pageType]/goods/goods-mgmt/goods-info-mgmt

## 다국어 Message Key 선언 방식

### 파일 생성 규칙

언어별, 모듈별 \*.json 파일 생성 또는 언어별·모듈별 path/업무별 \*.json 생성하여 언어별·모듈별로 다국어 메시지를 관리합니다. \*.json 파일명은 camelCase 형식으로 명명합니다.

* \*.json 파일 생성 방식
  * 모듈별 \*.json 파일 생성\
    예) `src/locales/langs/[ko, en, ja, ...]/goods.json`
  * 모듈별 path/업무별 \*.json 파일 생성\
    예) `src/locales/langs/[ko, en, ja, ...]/display/mallMgmt.json`
* 공통팝업에서 사용 시 `baseInfoMgmt.label.popup` 이후 key만 사용\
  예) `baseInfoMgmt.label.popup.displayCategoryListPopup.title`

```json
{ 
  "displayCategoryListPopup": { 
    "title": "전시 카테고리 조회 ",
    "...": "..." 
  } 
}
```

* 공통 필드(`sysModDtm`, `sysRegDtm` 등)는 `common.json` 파일 내 선언

## 파일명 규칙

업무별 명확한 폴더 분리를 하며, kebab-case, 파일타입은 ts로 제한합니다.

### 디렉토리 구조

* **utils**: 공통 및 업무별 유틸을 정의합니다.\
  예) `~/utils/모듈명/업무-utils.ts` — `~/utils/common/common-utils.ts`
* **types**: 타입스크립트의 타입을 정의합니다.\
  예) `~/types/모듈명/업무-types.ts` — `~/types/goods/goods-mgmt-types.ts`
* **schema**: 업무별 zod 스키마를 정의합니다.\
  예) `~/schema/모듈명/업무-schema.ts` — `~/schema/goods/goods-mgmt-schema.ts`
* **api**: 업무별 API 호출을 정의합니다.\
  예) `~/api/모듈명/업무-api.ts` — `~/api/display/standard-category-api.ts`
* **constants**: 업무별 상수값 정보를 정의합니다.\
  예) `~/constants/모듈명/업무-constants.ts` — `~/constants/display/display-constants.ts`
* **grid**: 업무·화면별 그리드 정보를 정의합니다. (BO)\
  예) `~/components/모듈명/화면기능명/업무/sample-grid.tsx` — `~/components/goods/goods-mgmt/goods-info/goods-info-grid.tsx`
* **grid-column**: 업무·화면별 그리드 컬럼 정보를 정의합니다. (BO)\
  예) `~/grid/모듈명/그리드_파일명.ts` — `~/grid/display/site-grid.ts`
* **search-form**: 업무·화면별 검색폼을 정의합니다.\
  예) `~/components/모듈명/화면기능명/업무/sample-search-form.tsx` — `~/components/display/site-mgmt/site-info-mgmt/site-search-form.tsx`
* **component 간 grouping section**: 업무·화면별 다수 컴포넌트를 그룹으로 묶을 때 정의합니다.\
  예) `site-grid.tsx`와 `site-search-form.tsx`를 묶고 싶은 경우 `~/components/display/site-mgmt/site-info-mgmt/site-search-section.tsx`
* **page.tsx 내부 선언 component**: 업무·화면별 페이지 컨텐츠 영역의 컴포넌트를 정의합니다.\
  예) `~/components/모듈명/화면기능명/업무/sample-contents.tsx` — `~/components/display/site-mgmt/site-info-mgmt/site-contents.tsx`
* **store**: zustand의 store를 모듈별로 정의합니다.\
  예) `~/store/모듈명/sample-store.ts` — `~/store/common/auth-store.ts`
* **hooks**: 공통·모듈별 공통 사용 부분을 hooks로 정의합니다.\
  예) `~/hooks/모듈명/use-sample.ts` 또는 `~/hooks/모듈명/업무/use-sample.ts` — `~/hooks/common/popup/use-popup-actions.ts`

## 공통팝업 및 다국어 팝업

* **공통팝업 폴더 생성 규칙**: 모듈·업무별 공통팝업을 `popup` 폴더 내에서 정의합니다.
  * 모듈별로 폴더를 생성\
    예) `~/app/popup/brand-list` → `~/app/popup/goods/brand-list`
* **다국어팝업 폴더 생성 규칙**: 다국어 관련 Context를 layout에 provider로 감싸서 정의합니다. 다국어 Context를 사용하기 위해서 다음 형식으로 정의합니다:\
  `~/app/(multi-lang)/(popup)/모듈명/*`\
  예) `~/app/(multi-lang)/(popup)/goods/brand-mgmt/brand-info-mgmt/brand-info-multi-lang`

## 코드 스타일

Lint와 Prettier를 적용하여 코드의 일관성을 유지합니다. npm 옵션을 이용해서 전체 소스에 대해 Lint, Prettier를 적용합니다.

* 전체 소스 Lint 적용: `npm run lint:fix`
* 전체 소스 Prettier 적용: `npm run fm:fix`


# API 개발 가이드

x2bee-api 프로젝트는 마이크로 서비스별 API를 제공하는 프로젝트입니다.

각 마이크로 서비스별로 구분되어 x2bee-api-display(전시), x2bee-api-order(주문) 등등의 프로젝트로 나뉘어져 있습니다.

x2bee-api의 모든 API는 REST API로 제공되며, JSON 형식으로 입력 값과 출력 값을 처리합니다.

x2bee-api에는 세션 정보와 같은 상태정보가 없습니다.\
API 처리에 필요한 정보는 그때그때 입력값으로 받거나 DB 조회해서 얻어야 하며, 불가피한 경우 캐시를 사용할 수도 있습니다.

x2bee-api는 클라이언트에서 호출되거나, 다른 x2bee-api에서 호출될 수 있습니다.

***

## 패키지명

업무 대분류를 기준으로 패키지를 분류하고 명명합니다.

예) 샘플 패키지/폴더

| 구분             | 패키지명/폴더명                                                                                   |
| -------------- | ------------------------------------------------------------------------------------------ |
| 컨트롤러           | com.x2bee.api.display.app.controller.sample                                                |
| 서비스            | com.x2bee.api.display.app.service.sample                                                   |
| 리포지토리          | com.x2bee.api.display.app.repository.sample                                                |
| DTO            | com.x2bee.api.display.app.dto.request.sample com.x2bee.api.display.app.dto.response.sample |
| mapper XML     | mapper/rwdb/sample mapper/rodb/display                                                     |
| message(다국어처리) | message/display                                                                            |

{% hint style="info" %}
entity/enum은 업무별 패키지 구분하지 않고 모두 entity/enum 패키지 아래에 작성합니다.
{% endhint %}

## 컨트롤러 작성

컨트롤러는 입력 파라미터를 받아서 DTO에 설정하고, 업무로직 처리를 위한 서비스 메소드를 호출한 후 서비스 메소드의 결과값을 적절한 응답 식으로 변환하여 return합니다.

### 클래스 어노테이션

클래스 레벨에 다음의 어노테이션을 사용합니다.

<table><thead><tr><th width="137.888916015625">종류</th><th width="232.7777099609375">어노테이션</th><th>설명</th></tr></thead><tbody><tr><td>Spring</td><td>@RestController</td><td>Rest 컨트롤러임을 표시하는 Spring bean 어노테이션. @ResponseBody + @Controller 역할을 합니다.</td></tr><tr><td></td><td>@RequestMapping</td><td>컨트롤러 클래스 수준의 Request Mapping URI 공통부분을 지정합니다.</td></tr><tr><td></td><td>@RequiredArgsConstructor</td><td>생성자 주입 편의를 위한 lombok 어노테이션입니다.</td></tr><tr><td></td><td>@Slf4j</td><td>로그 작성을 위해 사용하는 lombok 어노테이션입니다.</td></tr><tr><td>Swagger3 UI</td><td>@Tag</td><td>Swagger API 그룹 설정 시 사용합니다. 태그 이름과 description을 추가할 수 있습니다.</td></tr></tbody></table>

예시:

```java
@RestController
@RequestMapping("/categories")
@Slf4j
@RequiredArgsConstructor
@Tag(name = "카테고리 관리 Controller", description = "카테고리 API")
public class CategoryController { ... }
```

### 메소드 어노테이션

메소드 레벨에 다음의 어노테이션을 사용합니다.

<table><thead><tr><th width="157.888916015625">종류</th><th width="248.3333740234375">어노테이션</th><th>설명</th></tr></thead><tbody><tr><td>HTTP 요청</td><td>@GetMapping</td><td>get 메소드(조회/검색) 시 사용</td></tr><tr><td></td><td>@PostMapping</td><td>post 메소드(등록) 시 사용</td></tr><tr><td></td><td>@PutMapping</td><td>put 메소드(전체수정) 시 사용</td></tr><tr><td></td><td>@PatchMapping</td><td>patch 메소드(일부수정) 시 사용</td></tr><tr><td></td><td>@DeleteMapping</td><td>delete 메소드(삭제) 시 사용</td></tr><tr><td>Swagger3 UI</td><td>@Operation</td><td>Swagger API 설명 설정 시 사용</td></tr><tr><td></td><td>@ApiResponse</td><td>Swagger API response 설정 시 사용</td></tr><tr><td></td><td>@Parameters / @Parameter</td><td>Swagger API parameter 설정 시 사용</td></tr></tbody></table>

예시:

```java
@Operation(summary = "카테고리 목록 조회", description = "해당 카테고리 목록을 조회한다")
@Parameters({
  @Parameter(name = "siteNo", description = "사이트번호 (x2bee.com : 1)", required = true, example = "1"),
  @Parameter(name = "useYn", description = "사용여부 (사용함: Y, 사용안함: N)", required = true, example = "Y")
})
@ApiResponses(value = {
  @ApiResponse(responseCode = "200", description = "몰 정보 조회 성공", content = @Content(schema = @Schema(implementation = Category.class))),
  @ApiResponse(responseCode = "400", description = "몰 정보 조회 실패", content = @Content(schema = @Schema(implementation = ErrorCode.class)))
})
@GetMapping(value="/trees")
public List<Category> getCategoryTreeList(PrDispCtgBaseRequest prDispCtgBaseRequest) throws Exception {
    ...
    return categoryTreeList;
}
```

### 매핑 URI 형식

@RequestMapping의 path 파라미터로 작성될 매핑 URI의 형식은 다음과 같습니다.

```
/api/<업무대분류명(패키지명)>/resource명 복수형/하위resource명 복수형
```

* /api/<업무대분류명(패키지명)>: context-path로 지정. 프로그램에서 지정하지 않음
* /resource명 복수형: 클래스레벨 @RequestMapping에서 지정
* /하위resource명 복수형: 메소드레벨 Mapping에서 지정. 없는 경우 생략

예) 카테고리 처리:

* 클래스레벨 RequestMapping: @RequestMapping("/api/display/categories")

주요 매핑 예시:

<table><thead><tr><th width="235">기능</th><th>Method 레벨 RequestMapping</th></tr></thead><tbody><tr><td>카테고리 트리 조회</td><td>@GetMapping("/trees")</td></tr><tr><td>카테고리 상세 조회</td><td>@GetMapping("/{id}")</td></tr><tr><td>카테고리 등록</td><td>@PostMapping("")</td></tr><tr><td>카테고리 수정</td><td>@PutMapping("/{id}")</td></tr><tr><td>카테고리 삭제</td><td>@DeleteMapping("/{id}")</td></tr><tr><td>카테고리 비전시 처리</td><td>@PatchMapping("/{id}?displayYn=false")</td></tr></tbody></table>

### 메소드 파라미터 어노테이션

메소드 파라미터에 다음의 어노테이션을 사용할 수 있습니다.

<table><thead><tr><th width="180">어노테이션</th><th>설명</th></tr></thead><tbody><tr><td>@RequestBody</td><td>모든 API 파라미터 전달은 Request body에 JSON 유형의 데이터를 사용합니다. 해당 파라미터를 입력받기 위해 @RequestBody를 사용합니다.</td></tr><tr><td>@Valid, @Validated</td><td>파라미터 모델클래스 멤버변수의 유효성을 체크합니다. 멤버변수에 @NotNull, @Size, @Min, @Max, @Digits, @Pattern 등의 제약을 주고 검증 실패 시 MethodArgumentNotValidException이 발생합니다.</td></tr></tbody></table>

예시:

{% code lineNumbers="true" expandable="true" %}

```java
public Response<String> savePrDispGoodsInfo(@RequestBody @Valid 
PrDispGoodsInfo prDispGoodsInfo) throws Exception {
 ... 
 }
```

{% endcode %}

### 메소드 리턴값

API 응답은 응답 데이터를 Response 객체로 감싸서 return합니다.\
클래스 레벨에 @RestController가 사용되었으므로 실제 응답값은 Java 객체를 JSON으로 변환한 값이 됩니다.

예시:

{% code lineNumbers="true" %}

```java
@GetMapping(value = "/CtpNames")
public Response<List<String>> getCtpNmList() throws Exception {
    return new Response(zipNoService.getCtpNmList());
}
```

{% endcode %}

Response 클래스는 응답값이 작성된 Timestamp와 처리 오류코드/메시지를 지정할 수 있으며, payload에는 실제 응답 데이터가 들어갑니다.

## 서비스 클래스 작성

서비스 클래스는 특정 업무의 핵심 로직을 처리합니다.

### 인터페이스/구현클래스 구분

소메뉴별로 1개의 서비스 클래스를 작성하며, 인터페이스와 구현 클래스를 구분합니다.

예) CategoryService / CategoryServiceImpl

### 서비스 어노테이션

클래스 레벨에 아래 어노테이션을 사용합니다.

<table><thead><tr><th width="265">어노테이션</th><th>설명</th></tr></thead><tbody><tr><td>@Service</td><td>서비스 클래스임을 표시하는 Spring bean 어노테이션</td></tr><tr><td>@Slf4j</td><td>로그 작성을 위한 lombok 어노테이션</td></tr><tr><td>@RequiredArgsConstructor</td><td>생성자 주입 편의를 위한 lombok 어노테이션</td></tr></tbody></table>

예시:

{% code lineNumbers="true" %}

```java
@Service
@Slf4j
@RequiredArgsConstructor
public class CategoryServiceImpl implements CategoryService { ... }

public interface CategoryService { ... }
```

{% endcode %}

### 메소드 구성

서비스 메소드는 리포지토리 메소드 호출, 타 서비스 API 호출 및 기타 업무로직 처리 등의 작업을 실행한 후 결과를 반환합니다. 핵심 업무로직이 구현되도록 구성합니다.

### 트랜잭션 처리

등록/수정/삭제 서비스 메소드에 **@Transactional** 어노테이션을 통해 명시적으로 관리합니다.

* @Transactional이 선언된 클래스 및 메소드는 ReadWrite 데이터베이스로 연결됩니다.
* 선언되지 않은 Service 메소드는 ReadOnly 데이터베이스로 연결됩니다.
* CRUD가 혼재된 경우 명시적으로 트랜잭션을 선언합니다.

예:

{% code fullWidth="true" expandable="true" %}

```java
@Transactional(propagation = Propagation.REQUIRED, readOnly = false, value="orderRwdbTxManager")
```

{% endcode %}

value에는 displayRwdbTxManager, orderRwdbTxManager, eventRwdbTxManager 등 트랜잭션 매니저를 명시해야 합니다.

트랜잭션이 정상 동작하려면 오타가 없도록 유의하세요.

## Mapper 작성

### Mapper interface 작성

DB 테이블별로 1개의 \*\*\*Mapper와 1개의 \*\*\*TrxMapper를 작성합니다.

* \*\*\*Mapper: select 문 (read-only)
* \*\*\*TrxMapper: insert/update/delete 문 (read-write)

readonly 맵퍼에 insert/update/delete문을 작성하면 오류가 발생하므로 반드시 구분하여 작성합니다.

Mapper는 interface 자바 파일과 SQL mapper XML 파일 두 개로 작성합니다. Interface에는 호출될 메소드 시그니처를 기록하고 SQL은 mapper XML에 기술합니다.

예: CategoryMapper.java

{% code lineNumbers="true" %}

```java
public interface CategoryMapper {
    public List<CategoryResponse> selectAllCategories();
    public Optional<CategoryResponse> selectCategoryById(Long id);
    public List<CategoryResponse> selectCategories(CategoryRequest request);
}
```

{% endcode %}

### Mapper XML 작성

Mapper 메소드 호출시 실행될 SQL을 작성합니다.

예: CategoryMapper.xml

{% code lineNumbers="true" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
   "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.x2bee.api.app.repository.category.CategoryMapper">
  ...
  <!-- 전체 샘플 조회 -->
  <select id="selectAllSamples" resultType="SampleResponse">
    /* SampleMapper.getStStdCd */
    <include refid="sampleList" />
  </select>

  <!-- 샘플 단건 조회 -->
  <select id="selectSampleById" parameterType="long" resultType="SampleResponse">
    /* SampleMapper.selectSampleById */
    select * from (
      <include refid="sampleList" />
    ) a where id = #{id}
  </select>
  ...
</mapper>
```

{% endcode %}

## DTO / Entity 작성

### Request DTO 작성

Request DTO는 조회/검색 컨트롤러 메소드의 파라미터를 담는 Bean 객체입니다.

* 조회/검색 조건 파라미터가 존재하는 메소드 작성 시 Request DTO를 작성하여 요청 파라미터 값을 받아야 합니다.
* DTO 클래스는 BaseCommonEntity 클래스를 상속받아 생성합니다. (생성자/수정자/생성일시/수정일시/페이징/엑셀다운로드 관련 정보 포함)
* DTO 클래스에는 @Alias 어노테이션을 사용하여 Mapper의 타입 축약어로 사용할 수 있도록 합니다.
* 컨트롤러에서 받은 정보를 별도의 수정 없이 서비스/Mapper 메소드에서 사용할 수 있습니다.
* serialVersionUID는 반드시 UUID를 생성하여 사용합니다.

예: PrDispGrpBaseRequest.java

{% code lineNumbers="true" %}

```java
@Alias("PrDispGrpBaseRequest")
@Getter 
@Setter
public class PrDispGrpBaseRequest extends BaseCommonEntity {
    private static final long serialVersionUID = 5756700830219562201L;
    @Schema(description = "그룹코드")
    private String dispGrpTypCd;
    @Schema(description = "그룹번호")
    private String dispGrpNo;
    ...
}
```

{% endcode %}

### Response DTO 작성

Response DTO는 조회/검색 컨트롤러 메소드의 결과값을 담는 Bean 객체입니다.

* 조회/검색 결과가 존재하는 메소드 작성 시 Response DTO를 작성합니다.
* DTO 클래스는 BaseCommonEntity를 상속하지 않습니다.
* @Alias 어노테이션을 사용하여 Mapper 메소드의 리턴타입으로 사용할 수 있도록 합니다.
* serialVersionUID는 반드시 UUID를 생성하여 사용합니다.

예: PrDispGrpBaseResponse.java

{% code lineNumbers="true" %}

```java
@Alias("PrDispGrpBaseResponse")
@Getter 
@Setter
public class PrDispGrpBaseResponse {
    private static final long serialVersionUID = 5756700830219562201L;
    @Schema(description = "그룹코드")
    private String dispGrpTypCd = ConstCode.DISP_GRP_TYP_CD_REP_MKDP;
    @Schema(description = "그룹번호")
    private String dispGrpNo;
    ...
}
```

{% endcode %}

### Entity 작성

Entity 클래스는 등록/수정/삭제 컨트롤러 메소드의 파라미터를 담는 Bean 객체입니다.

* 등록/수정/삭제 메소드 작성 시 Entity 클래스를 통해 요청 파라미터 값을 받습니다.
* Entity 클래스는 필드와 DB 테이블 필드와 동일하게 구성합니다.
* Entity 클래스는 BaseCommonEntity 클래스를 상속받아 생성합니다.
* @Alias 어노테이션을 사용하여 Mapper XML에서 사용할 수 있도록 합니다.
* serialVersionUID는 반드시 UUID를 생성하여 사용합니다.

예: PrDispCtgBase.java

{% code lineNumbers="true" %}

```java
@Alias("prDispCtgBase")
@Getter 
@Setter
public class PrDispCtgBase extends BaseCommonEntity {
    private static final long serialVersionUID = 5756700830219562201L;
    @Schema(description = "카테고리코")
    private String dispCtgNo;
    @Schema(description = "카테고리명")
    private String dispCtgNm;
    ...
}
```

{% endcode %}

### 오류처리

오류 발생 시 ApiException()을 throw합니다. 생성자 파라미터로 ApiError를 지정합니다.

ApiError는 enumeration으로 오류유형과 메시지 key를 포함한 오류 상수들로 구성되어 있습니다.

예:

{% code lineNumbers="true" %}

```java
@GetMapping("/error")
public Response<String> getError() {
    if (true) {
        throw new ApiException(ApiError.UNKNOWN);
    }
    return new Response<String>();
}
```

{% endcode %}

해당 예시 호출 시 반환되는 응답 예: HTTP status: 400 BAD REQUEST

```json
{
  "timestamp": "2021-09-13T17:30:42.132",
  "code": "9000",
  "message": "알 수 없는 오류입니다.",
  "payload": null
}
```

ApiError 예시:

{% code lineNumbers="true" %}

```java
@Getter
@AllArgsConstructor
public enum ApiError implements AppError {
    // success
    SUCCESS("0000", "common.message.success", "common.message.success", false),
    // app error
    EMPTY_PARAMETER("1001", "common.error.emptyParameter", "common.error.emptyParameter", false),
    INVALID_PARAMETER("1002", "common.error.invalidParameter", "common.error.invalidParameter", false),
    DATA_NOT_FOUND("1003", "common.error.dataNotFound", "common.error.dataNotFound", false),
    DUPLICATE_DATA("1004", "common.error.duplicateData", "common.error.duplicateData", false),
    INVALID_FILE("1005", "common.error.invalidFile", "common.error.invalidFile", false),
    UPLOAD_FAIL("1100", "common.error.uploadFail", "common.error.uploadFail", false),
    MEMBER_API_FAIL("1200", "common.error.memberApi", "common.error.memberApi", false),
    // unknown error
    UNKNOWN("9000", COMMON_ERROR_UNKNOWN_MSG_CD, COMMON_ERROR_UNKNOWN_MSG_CD, false),
    // ValidationException error
    VALIDATION_EXCEPTION("9100", COMMON_ERROR_UNKNOWN_MSG_CD, COMMON_ERROR_UNKNOWN_MSG_CD, false);

    private final String code;
    private final String messageKey;
    private final String boMessageKey;
    private final boolean isProcess;

    @Override
    public boolean getIsProcess() {
        return isProcess;
    }
}
```

{% endcode %}

메시지용 common.properties (예시)

* common.error.emptyParameter = 파라미터 값이 없습니다: {0}
* common.error.invalidParameter = 파라미터가 올바르지 않습니다.
* common.error.dataNotFound = 데이터가 존재하지 않습니다.
* common.error.bindingError = 파라미터 바인딩 오류입니다.
* common.error.bindingErrorNotNull = 파라미터 바인딩 오류입니다: not null
* common.error.unknown = 알 수 없는 오류입니다.
* common.message.success = 성공

## 프로퍼티

* application.yml: 스프링 설정값이나 base 클래스 설정값을 관리합니다.
* config/application-\<profile명>.properties: 업무 개발 시 필요한 프로퍼티 항목들을 관리합니다. 각 업무 개발자가 필요 시 항목을 추가할 수 있습니다.

java에서 프로퍼티 사용 예:

```java
@Value("${app.apiUrl.system}")
private String systemApiUrl;
```

environment 빈을 통한 조회 예:

```java
String uploadDomain = environment.get("domain.baseUrl");
```

## 메시지 사용

메시지는 src/main/resources/message 폴더에 관리합니다. 업무 대분류별로 개별 폴더를 생성하고 컨트롤러별로 메시지 파일을 관리합니다.

### java에서 메시지 사용

MessageResolver 클래스를 사용하여 메세지를 조회합니다. 메세지 key로 조회하거나 AppError enum을 사용할 수 있습니다. 메세지는 다국어를 지원하며, LocalContext에 저장된 locale에 따라 언어를 구분하거나 메소드 호출 시 Locale을 직접 지정할 수 있습니다.

MessageResolver 메소드 형식 예:

{% code lineNumbers="true" %}

```
public static String getMessage(String messageKey);
public static String getMessage(String messageKey, Object[] args);
public static String getMessage(String messageKey, Locale locale);
public static String getMessage(String messageKey, Object[] args, Locale locale);

String getMessage(AppError appError);
public static String getMessage(AppError appError, Object[] args);
public static String getMessage(AppError appError, Locale locale);
public static String getMessage(AppError appError, Object[] args, Locale locale);
public static String getMessage(AppError appError, Object[] args, String defaultMessage);
public static String getMessage(AppError appError, Object[] args, String defaultMessage, Locale locale);
```

{% endcode %}

### 로그

* 로깅 라이브러리: logback 사용 (설정: logback-spring.xml). slf4j 인터페이스 사용.
* 로그 작성: @Slf4j lombok 어노테이션 사용 후 log 객체로 작성.

예:

```
log.debug("로그테스트 합니다: {}", obj1);
log.info("로그테스트 합니다: {}", obj1);
log.warn("로그테스트 합니다: {}", obj1);
log.error("로그테스트 합니다: {}", obj1);
```

* 로그 레벨 사용: error(오류 상황), debug(디버그 용)
* 로그 조회: 로컬 개발 환경에서는 콘솔 또는 파일로 조회. \
  기본 로그 파일 경로: c:/X2BEE-DEV/log/x2bee-\*\*\*.log\
  개발/운영 환경에서는 설정된 Kibana를 통해 로그 조회 가능 (접속정보 별도 확인).

## 마스킹(Masking)

### 동작원리

Request DTO에 @Masking 필드 어노테이션을 적용하고 type을 지정하면, SQL 조회 시 해당 @Masking 적용된 DTO 필드에 대해 지정된 type 유형의 masking이 자동 적용됩니다.

예: MaskingCUD.java

{% code lineNumbers="true" %}

```java
@Alias("MaskingCUD")
@Getter @Setter @ToString
public class MaskingCUD {
    @MaskString(type = MaskingType.NAME_KR)
    private String userNmKr; // 성명_한글

    @MaskString(type = MaskingType.NAME_EN)
    private String userNmEn; // 성명_영문

    @MaskString(type = MaskingType.BIRTH)
    private String userBirth; // 생년월일

    @MaskString(type = MaskingType.RRN)
    private String userRrn; // 주민번호

    @MaskString(type = MaskingType.PHONE_NUM)
    private String userPhoneNum; // 전화번호

    @MaskString(type = MaskingType.MOBILE_NUM)
    private String userMobileNum; // 핸드폰

    @MaskString(type = MaskingType.ADDRESS)
    private String userAddress; // 주소

    @MaskString(type = MaskingType.ADDRESS_DTL)
    private String userAddressDtl; // 상세주소

    @MaskString(type = MaskingType.IP)
    private String userIP; // IP

    @MaskString(type = MaskingType.EMAIL)
    private String userEmail; // 이메일

    @MaskString(type = MaskingType.ID)
    private String userID; // ID

    @MaskString(type = MaskingType.ACTN)
    private String userActn; // 계좌번호

    @MaskString(type = MaskingType.CARD)
    private String userCard; // 카드번호
}
```

{% endcode %}

### 마스킹 유형

<table><thead><tr><th width="143">마스킹유형</th><th width="148" align="right">유형코드</th><th>최소요건</th><th>설명</th></tr></thead><tbody><tr><td>이름</td><td align="right">NAME_KR</td><td>이름 두번째 마스킹</td><td>홍<em>동 / 을</em>문덕</td></tr><tr><td>영문이름</td><td align="right">NAME_EN</td><td>이름 중 첫번째와 마지막 알파벳 제외하고 마스킹</td><td>John Smith → J**h Smith</td></tr><tr><td>전화번호</td><td align="right">MOBILE_NUM</td><td>중간번호 전체 마스킹</td><td>010****2134</td></tr><tr><td>주소 (동이하)</td><td align="right">ADDRESS</td><td>동이하 주소 전체 마스킹</td><td>서울 강남구 압구정동 ****</td></tr><tr><td>도로명(길이하)</td><td align="right">ADDRESS</td><td>길이하 주소 전체 마스킹</td><td>서울 강남구 압구정로 ****</td></tr><tr><td>상세주소</td><td align="right">ADDRESS_DTL</td><td>상세주소 전체 마스킹</td><td>-</td></tr><tr><td>이메일</td><td align="right">EMAIL</td><td>아이디 4번째부터 끝까지 마스킹</td><td>abc***@**********</td></tr><tr><td>주민번호</td><td align="right">RRN</td><td>주민번호 뒤 7자리 이상 마스킹</td><td>801212-*******</td></tr><tr><td>생년월일</td><td align="right">BIRTH</td><td>일 자리 마스킹</td><td>1980-12-**</td></tr><tr><td>운전면허번호</td><td align="right">LICENSE</td><td>5번째부터 6자리 이상 마스킹</td><td>서울 95-******-61</td></tr><tr><td>여권번호</td><td align="right">PASSPORT</td><td>뒤 4자리 이상 마스킹</td><td>M9999****</td></tr><tr><td>현금영수증카드</td><td align="right">CARD</td><td>9번째부터 4자리 이상 마스킹</td><td>1544-2020-****-123456</td></tr><tr><td>신용카드(14자리)</td><td align="right">CARD</td><td>8번째부터 4자리 이상 마스킹</td><td>9500-0012-****-0000</td></tr><tr><td>기타카드(11자리)</td><td align="right">CARD</td><td>7번째부터 4자리 이상 마스킹</td><td>9500-00**-**0</td></tr><tr><td>기타카드(13~19자리)</td><td align="right">CARD</td><td>8번째부터 4자리 이상 마스킹</td><td>9500-0012-****-0000</td></tr><tr><td>사업자등록번호</td><td align="right">BNO</td><td>3번째부터 4자리 이상 마스킹</td><td>12*-**-*1234</td></tr><tr><td>계좌번호</td><td align="right">ACTN</td><td>6번째부터 끝까지 마스킹</td><td>12345**********</td></tr><tr><td>QR코드</td><td align="right">QRCODE</td><td>5번째부터 4자리 이상 마스킹</td><td>126-0-****-1234</td></tr><tr><td>IP</td><td align="right">IP</td><td>7번째부터 3자리 이상 마스킹</td><td>123.123.***.123</td></tr><tr><td>ID</td><td align="right">ID</td><td>4자리부터 끝까지 마스킹</td><td>kim******</td></tr></tbody></table>

### MaskingUtils 사용

개별 데이터 마스킹이 필요한 경우 MaskingUtils를 사용할 수 있습니다.

| 메소드                                           | 설명                             |
| --------------------------------------------- | ------------------------------ |
| masking(String src, int startIdx)             | 문자의 startIdx에서 끝까지 마스킹         |
| masking(String src, int startIdx, int length) | 문자의 startIdx에서 length 길이만큼 마스킹 |

## DB 데이터 필드 암복화

### 동작원리

Request DTO 및 Entity의 암호화 대상 필드에 @Encrypt 필드 어노테이션을 적용합니다.\
Mapper에서 SQL insert/update 시 해당 @Encrypt 적용된 필드가 암호화되어 DB에 저장되고, select 시 복호화 되어 조회됩니다.

예:

<pre class="language-java" data-line-numbers><code class="lang-java">@Alias("EncryptCUD")
@Getter 
@Setter
<strong>public class EncryptCUD {
</strong><strong>    @Encrypt
</strong><strong>    private String userNmKr; // 성명_한글 암복호화
</strong><strong>
</strong><strong>    @Encrypt
</strong><strong>    private String userNmEn; // 성명_영문 암복호화
</strong><strong>}
</strong></code></pre>

### 적용 알고리즘

암복화에 AES-256-GCM을 사용합니다. 적용 알고리즘 요약:

<table><thead><tr><th width="270">항목</th><th>구성</th></tr></thead><tbody><tr><td>암호화 알고리즘</td><td>AES-256-GCM</td></tr><tr><td>데이터 암호화 키 길이(비트)</td><td>256</td></tr><tr><td>키 추출 알고리즘</td><td>HKDF (SHA-384 포함)</td></tr><tr><td>서명 알고리즘</td><td>P-384 및 SHA-384 포함된 ECDSA</td></tr><tr><td>기간 약정</td><td>HKDF (SHA-512 포함)</td></tr></tbody></table>

### 암호문 데이터 길이

암호화된 데이터 길이는 평문보다 길어지며, DB 필드 설계 시 고려해야 합니다.

암호화 필드 길이 계산식

```
암호화필드길이 = 880 + (원래필드길이 * 4/3)
```

### EncryptUtils 사용

개별 암호화/복호화가 필요한 경우 EncryptUtils의 static 메소드를 사용합니다.

* 암호화: public static String getEncryptValue(String value) throws Exception;
* 복호화: public static String getDecryptValue(String value) throws Exception;

### 암호화 key 환경변수

암호화/복호화 시 사용될 key는 application.yml에 관리됩니다. 암호화 키는 32자리 입력.

예:

{% code lineNumbers="true" %}

```yaml
crypto:
  secret:
    key: X2BEE_Application_DATA_SecretKey
```

{% endcode %}

## 메소드 권한 및 로그인 사용자 정보

예시:

```java
@Secured("ROLE_MEMBER")
@GetMapping("/{id}/secure")
public ResponseEntity<Response<List<SampleResponse>>> getSampleUser(
    @AuthenticationPrincipal UserDetail userDetail,
    @RequestBody SampleRequest sampleRequest) throws Exception {

    if (!userDetail.getMbrNo().equals(sampleRequest.getMbrNo())) {
        AppException.exception(ApiError.NOT_AUTHORIZED);
    }
    return restApiService.get(getUrl("/search"), sampleRequest);
}
```

* @Secured("ROLE\_MEMBER")을 사용하면 회원 로그인된 경우만 진입 가능.
* 로그인 상태가 아니면 호출 시 HTTP 403 FORBIDDEN 발생.
* 컨트롤러 메소드에서 로그인 사용자 정보는 @AuthenticationPrincipal로 주입받아 사용합니다.
* userDetail.getMbrNo()로 회원번호 조회 가능.

## Swagger3 @Schema

<table><thead><tr><th width="143">어노테이션</th><th width="203">속성</th><th>설명</th></tr></thead><tbody><tr><td>@Schema</td><td>description</td><td>한글명</td></tr><tr><td></td><td>defaultValue</td><td>기본값</td></tr><tr><td></td><td>allowableValues</td><td>허용 가능한 값(열거형)</td></tr><tr><td></td><td>example</td><td>예시값</td></tr></tbody></table>

Request/Response DTO 작성 시 위 어노테이션과 속성을 설정하여 Swagger UI에서 확인할 수 있습니다.

***


# BO 개발 가이드

X2BEE 솔루션 3.0의 일관된 개발 스타일을 위해 사용되는 프로그래밍 표준을 설명합니다. 일관된 코드 스타일을 유지하며, 솔루션을 활용한 개발의 효율성과 가독성을 위해 가이드라인을 준수 해야합니다.

Next.js 15 기반의 애플리케이션 개발을 위한 코드 재사용성, 타입 안정성, 성능 최적화 원칙과 함께 프로젝트의 구조와 개발 표준을 설명합니다.

***

## Route 생성 가이드

* NEXT15 - [App Router](https://nextjs.org/docs/app) 사용
* URL은 폴더 계층대로 [약어집](/dev-guide/pjt-prepare/publish-your-docs#undefined-3)을 기반으로 조합하여 생성
* 1차분류 : *<mark style="color:red;">모듈</mark>* , 2차분류 : *<mark style="color:green;">업무</mark>*
  * 예) /src/app/\[pageType]/*<mark style="color:red;">**display**</mark>*/*<mark style="color:green;">**standard-category-mgmt**</mark>*/page.tsx → /display/standard-category-mgmt
* 페이지 컴포넌트(page.tsx) → use client 사용을 최소화

표준 구조 예시:

<table data-header-hidden><thead><tr><th width="95"></th><th width="133"></th><th width="85"></th><th width="80"></th><th></th></tr></thead><tbody><tr><td>app</td><td>(multi-lang)</td><td>모듈</td><td>업무</td><td>다국어 적용을 위한 <a href="https://nextjs.org/docs/app/building-your-application/routing/route-groups">그룹</a></td></tr><tr><td></td><td>(task-popup)</td><td></td><td></td><td>업무팝업 <a href="https://nextjs.org/docs/app/building-your-application/routing/route-groups">그룹</a> ex) 전시연결세트 팝업</td></tr><tr><td></td><td>[pageType]</td><td></td><td></td><td><p>pageType: 'page' | 'tab' | 'pagepopup'</p><p>default 값을 넣지 않으면 layout에서 page로 기본 설정</p></td></tr><tr><td></td><td>popup</td><td></td><td></td><td>공통 및 일반 팝업</td></tr><tr><td></td><td>auth</td><td></td><td></td><td>권한관련 (로그인 페이지)</td></tr><tr><td></td><td>error</td><td></td><td></td><td>에러페이지</td></tr></tbody></table>

***

## Component 생성 가이드

* 1차분류 : *<mark style="color:red;">**모듈**</mark>* , 2차분류 : *<mark style="color:green;">**업무**</mark>*
  * 예) /src/components/*<mark style="color:red;">**display**</mark>*/*<mark style="color:green;">**display-category-mgmt**</mark>* /display-category-tree.tsx
* 업무별 필수 type, schema 별도 파일로 정의

{% code title="standard-category-schema.ts" lineNumbers="true" %}

```typescript
export const StandardCategoryGoodsAttrSchema = z.object({
  stdCtgNo: StringSchema({ key: 'display.standardCategory.field.stdCtgNo' })
});
export type StandardCategoryGoodsAttrSchemaType = z.infer<typeof StandardCategoryGoodsAttrSchema>;
```

{% endcode %}

* 컴포넌트 내부에서만 사용될 것으로 판단되는 타입은 컴포넌트 내부에 선언

{% code title="StandardCategoryGoodsAttrGrid.tsx" lineNumbers="true" %}

```typescript
type Props = {stdCtgNo: string };
export default function StandardCategoryGoodsAttrGrid({ stdCtgNo }: Props) { 
... }
```

{% endcode %}

* 업무 및 state 기준으로 컴포넌트를 세밀하게 구성하여 리렌더링(Re-render) 이슈 최소화
* key 설정으로 컴포넌트의 life-cycle을 명시적으로 관리 가능
  * key는 상세하게 남겨 주는 것을 권장 (단순 ID만 사용할 경우 키 중복 가능)

예시:

{% code title="usage-example.tsx" lineNumbers="true" %}

```jsx
<CommonSectionBox>
  <StandardCategoryForm
    standardCategory={standardCategory}
    key={`standard_category_form_${standardCategory.stdCtgNo}`}
  />
</CommonSectionBox>
```

{% endcode %}

컴포넌트 내부 예시:

{% code title="StandardCategoryForm.tsx" lineNumbers="true" %}

```tsx
const StandardCategoryForm = ({ standardCategory }: Props) => {
  const { data: formData, success } = useSafeParse(
    StandardCategorySchema.safeParse(standardCategory)
  );
  ...
}
```

{% endcode %}

***

## API 호출 가이드

* REST API 사용
* Zod 스키마에서 추출된 타입 사용
* Promise 형식 반환을 기본으로 함

**GET 예시:**

{% code title="api-get.ts" lineNumbers="true" %}

```ts
export const fetchDisplaySubCategoryList = async (
  params: DisplayCategorySearchSchemaType
) => 
  (await restApi.get(
    `${API_PATH}/getSubCategoryList`, 
    { 
      params 
    }
  )) as GridResponse<DisplayCategorySchemaType[]>;
```

{% endcode %}

**POST/PUT/DELETE 예시:**

{% code title="api-post.ts" lineNumbers="true" %}

```ts
export const fetchRegistDisplayCategory = async (
  body: DisplayCategorySchemaType
) => 
  (await restApi.post(
    API_PATH, 
    {
     body 
    }
  )) as ResponseEntity<DisplayCategorySchemaType>;
```

{% endcode %}

**핸들러 및 클라이언트 API 사용 규칙:**

* handleResponse, handleGridResponse : getData 내부에서 isSuccess로 기본 데이터 출력
  * isSuccess: boolean
  * code: string
  * message: string
  * getData: () => T
* clientRestApi : clientRequest 시 사용, 기본 옵션으로 로딩 enabled
  * api: Promise
  * options(optional) → loading: true

사용 예시:

{% code title="useClientRestApi-example.tsx" lineNumbers="true" %}

```ts
const clientRestApi = useClientRestApi();

const fetchGridData = useCallback(async () => {
  const api = async () => {
    const response = await fetchDisplaySubCategoryList({ dispCtgNo: displayCategory.dispCtgNo });
    const { getData } = handleGridResponse(response as GridResponse<DisplayCategorySchemaType[]>);
    const payload = getData() as X2beeSimplePaginationDataType<DisplayCategorySchemaType>;
    setGridPayload(payload.rows);
  };

  await clientRestApi({
    api,
    // options: { loading: true } // 옵션 생략 가능
  });
}, [displayCategory.dispCtgNo]);
```

{% endcode %}

***

## Custom Hooks 생성 가이드

* 재사용이 필요하고 hooks 사용이 필요할 경우 생성
* 로직 복잡도로 인해 별도로 관리를 해야할 경우 (Utils 선고려, 내부에서 Hooks 사용 여부 고려)

***

## Utils 생성 가이드

* 재사용이 필요한 경우
* 내부에서 hooks 사용이 되지 않는 경우
* 구문이 복잡하여 별도로 관리가 필요한 경우
* 서버 영역에서 사용이 필요할 경우

***

##


# Batch 개발 가이드

본 문서에서는 X2BEE 배치 시스템 구성 및 Spring Boot Batch에 대해 설명합니다.

먼저, Cronicle 스케줄러의 작성 방법과 소스 코드 작성 절차를 설명하고, 이를 토대로 간단한 배치 작업을 수행하는 샘플 프로그램 목록을 제공합니다.

***

## X2BEE 배치 시스템 구성

다음은 배치 시스템 전체 구성도 입니다.

<figure><img src="/files/68jAMbh01ybWcQmnoRE1" alt=""><figcaption></figcaption></figure>

<table><thead><tr><th width="136">구분</th><th>설명</th></tr></thead><tbody><tr><td>배치서비스</td><td>배치 업무를 수행하는 프로그램입니다. 스케줄러에 의해 정해진 주기로 기능이 실행되거나 즉시 실행될 수 있습니다. 데이터 저장소의 데이터를 조회하여 처리 후 다시 데이터 저장소에 저장하는 것이 주요 업무입니다. Spring batch를 사용하여 구현되며, Web 서비스로 동작하여 HTTP 요청을 통해 배치 잡(job)을 실행할 수 있습니다.</td></tr><tr><td>스케줄러</td><td>배치 잡(job)과 그 실행주기를 등록하고 관리합니다. 실행 주기가 되었을 때 배치 잡이 실행되도록 배치 서비스에 HTTP 요청을 보내고, 필요에 따라 즉시 실행 기능을 제공합니다. 실행 결과 로그를 남기고 실행 결과 통계를 UI를 통해서 제공합니다. 공개소프트웨어를 사용합니다.</td></tr><tr><td>BO배치관리</td><td>필요에 따라 보다 세부적인 배치 관리 기능을 BO 시스템에 커스텀으로 구현합니다. 데이터 처리 및 통계 및 즉시 중지 기능 등을 제공합니다.</td></tr><tr><td>데이터저장소</td><td>대상 데이터는 제한이 없으며 DB, File, S3, Queue 등을 사용할 수 있습니다.</td></tr></tbody></table>

***

## Spring boot batch 설명

배치 서비스는 Spring Batch를 사용하여 개발합니다.

Spring Batch는 로깅/추적, 트랜잭션 관리, 작업 처리 통계, 작업 재시작, 건너뛰기 및 리소스 관리와 같이 대량의 레코드를 처리하는 데 필요한 재사용 가능한 핵심 기능을 제공합니다. 또한 최적화 및 파티셔닝 기술을 활용하여 고성능 배치 작업을 효과적으로 수행할 수 있는 고급 기술과 기능을 제공합니다.

### Spring Batch 프로그램 구조

Spring Batch 프로그램 구조는 Tasklet 기반과 Chunk 기반으로 나뉩니다.

**Chunk 기반**

* 한 번에 고정된 양의 레코드(Chunk)를 읽고 처리하는 방식입니다.
* Chunk 단위로 트랜잭션을 수행하기 때문에 실패 시 해당 Chunk만 롤백됩니다.
* 페이징 크기와 커밋 간격(Chunk Size)을 일치시키는 것이 권장됩니다.
* 사용 예: 대량 데이터 변경 작업

중요 개념:

* Job, Step: 작업의 최소 단위. 하나의 Job은 하나 이상의 Step으로 구성됩니다.
* Chunk: 트랜잭션의 관리 단위.
* reader/processor/writer: 데이터를 읽고/가공하고/저장하는 컴포넌트

<figure><img src="/files/FGgbnBdKQwjzN3240IfZ" alt=""><figcaption></figcaption></figure>

Tasklet 기반

* 단일 태스크를 수행하는 방식입니다. execute 메서드를 반복 호출하여 작업을 수행합니다.
* 초기화, 저장 프로시저 실행, 알림 전송 등에서 사용됩니다.
* 간단한 배치는 Tasklet으로 쉽게 구현되지만, 대량 처리는 복잡해질 수 있습니다.

<figure><img src="/files/XZ0jikAkLOHT3DMpCW6R" alt=""><figcaption></figcaption></figure>

***

## cronicle 설명

cronicle은 웹 기반 프런트 엔드 UI를 갖춘 다중 서버 작업 스케줄러 및 실행기입니다. 실시간 통계 및 라이브 로그 뷰어를 통해 여러 슬레이브 서버를 대상으로 예약된 작업, 반복 작업 및 주문형 작업을 처리합니다.

기능 요약:

* 단일 또는 다중 서버 설정
* 백업 서버에 대한 자동 장애 조치
* 근접 서버 자동 검색
* 라이브 로그 뷰어
* 다양한 시간대에 이벤트 예약
* 이벤트 대기열 관리(옵션)
* CPU 및 메모리 사용량 추적
* 과거 통계 및 성능 그래프
* 웹후크 지원
* 이벤트 예약 및 실행을 위한 REST API

### cronicle 이벤트 작성

로그인 후 Schedule 탭 하단의 Add Event를 통해 이벤트를 생성합니다.

<figure><img src="/files/km0uxSgyEFkvMpZqaQyN" alt=""><figcaption></figcaption></figure>

주요 속성:

* Event Name: 이벤트 이름
* Category: 이벤트 카테고리
* Plugin: 실행될 플러그인
* Target: 실행 서버 대상
* Timing: 실행 주기(일일, 시간별 등)

<figure><img src="/files/I1KN6OiCVXifqHnb7Vyw" alt=""><figcaption></figcaption></figure>

타이밍은 시각적 다중 선택기 위젯을 사용하여 다양한 날짜/시간 예약이 가능합니다. Crontab 설정을 사용하려면 \[ Import… ] 링크를 통해 입력할 수 있습니다.

***

## 컨트롤러 작성

HTTP 요청을 통해 배치 Job을 기동하는 데 사용됩니다. 공통 Controller(BatchJobController)를 사용하면 별도 컨트롤러를 작성하지 않아도 됩니다. 별도의 기능이 필요한 경우에만 컨트롤러를 작성합니다.

### 클래스 어노테이션

클래스 레벨에 다음 어노테이션을 사용합니다.

<table><thead><tr><th width="250.6666259765625">어노테이션</th><th>설명</th></tr></thead><tbody><tr><td>@RestController</td><td>REST 컨트롤러임을 표시 (@ResponseBody + @Controller 역할)</td></tr><tr><td>@RequestMapping</td><td>컨트롤러 클래스 수준의 공통 URI 지정</td></tr><tr><td>@Slf4j</td><td>lombok 로그 어노테이션</td></tr><tr><td>@RequiredArgsConstructor</td><td>lombok 생성자 주입 편의 어노테이션</td></tr></tbody></table>

예시:

```java
@RestController
@RequestMapping("/samples")
@Slf4j
@RequiredArgsConstructor
public class SampleParamJobController { ... }
```

### 매핑 URI 형식

@RequestMapping의 path 파라미터 형식: /batch/\<mbod,gddp>/\<resource명 복수형>/<배치Job명>

* /batch/\<mbod,gddp>: context-path로 지정 (프로그램에서 지정하지 않음)
* resource명 복수형: 클래스 레벨 @RequestMapping에서 지정
* 배치Job명: resource명을 Job명 앞에 붙이는 형식

예:

* context-path: /batch/gddp
* 클래스 레벨 RequestMapping: @RequestMapping("/categories")
* 개별 매핑 예:
  * @GetMapping("/categoryRankPopularJob")
  * @GetMapping("/categoryRankPopularGoodsJob")

### 메소드 파라미터 어노테이션

| 어노테이션         | 설명                                  |
| ------------- | ----------------------------------- |
| @PathVariable | JobName은 PathVariable 형식을 사용        |
| @RequestParam | 파라미터는 QueryString 또는 FormData 방식 사용 |

예시 시그니처:

```java
@GetMapping("/{jobName}")
public String sampleJobs(@PathVariable String jobName,
                         @RequestParam MultiValueMap<String, String> parameters) throws Exception { ... }
```

### 기본 소스코드 (예시)

```java
public class SampleParamJobController {
    private final JobLauncher jobLauncher;
    private final BatchCommonService batchCommonService;
    // job 선언, DI
    private final Job sampleParamJob;

    @GetMapping("/sampleParamJob")
    public String sampleParamJob(@RequestParam MultiValueMap<String, String> parameters) throws Exception {
        // 파라미터 준비 + increment(중복실행허용)
        JobParameters jobParameters = batchCommonService.setIncrementer(sampleParamJob);
        jobParameters = new JobParametersBuilder(jobParameters)
            .addString("sampleParam", parameters.getFirst("sampleParam"))
            .toJobParameters();

        // 실행
        JobExecution jobExecution = jobLauncher.run(sampleParamJob, jobParameters);
        Log.info("Batch job has been invoked: {}", jobExecution);

        // 실행성공
        return "Batch job has been invoked";
    }
}
```

아래는 위 예시의 주요 단계들을 stepper로 정리한 내용입니다.

{% stepper %}
{% step %}

### Job 멤버 선언 및 DI

* Job을 클래스 멤버로 선언하고 의존성 주입(DI) 처리합니다.
  {% endstep %}

{% step %}

### 중복 실행 허용 및 파라미터 준비

* incrementer를 통해 중복 실행 관리.
* JobParametersBuilder로 필요한 파라미터를 추가합니다.
  {% endstep %}

{% step %}

### Job 실행

* JobLauncher.run(job, jobParameters)으로 Job을 실행합니다.
* 실행 결과는 Spring Batch repository에서 확인합니다.
  {% endstep %}
  {% endstepper %}

### 메소드 리턴값

컨트롤러의 반환값은 단순 String을 사용합니다. 호출 결과만 리턴하고, 상세 실행 결과는 Spring Batch repository에서 확인합니다.

***

## Job Configuration 클래스 작성

Job Configuration 클래스는 Spring Batch에서 Job, Step, Reader/Writer/Processor 등의 Bean을 정의하는 클래스입니다.

요약:

* Java 17 이상에서 지원
* 기존의 StepBuilderFactory, JobBuilderFactory는 더 이상 권장되지 않음. 대신 JobRepository와 PlatformTransactionManager를 명시적으로 사용
* @EnableBatchProcessing은 더 이상 사용하지 않아도 됨(권장하지 않음)

예시는 CompositeItemWriter 방식으로 작성되었습니다.

### 클래스 레벨 어노테이션

| 어노테이션                    | 설명                   |
| ------------------------ | -------------------- |
| @Configuration           | Job, Step 등 Bean 선언용 |
| @Slf4j                   | lombok 로그            |
| @RequiredArgsConstructor | lombok 생성자 주입 편의     |

예시:

```java
@Configuration
@RequiredArgsConstructor
@Slf4j
public class SimpleJobConfig {
    private final JobRepository jobRepository;
    private final PlatformTransactionManager transactionManager;
    ...
}
```

### Job 생성 (예시)

```java
@Bean
public Job simpleComposItemWriterJob() {
    return new JobBuilder("simpleComposItemWriterJob", jobRepository)
        .start(simpleComposItemWriterStep()) // Step 설정
        .incrementer(new UniqueRunIdIncrementer()) // 중복실행허용
        .build();
}
```

설명:

* 일반적으로 하나의 Step을 구성하고 필요시 nextStep을 추가
* incrementer로 중복 실행 관리 가능
* JobParametersValidator를 사용해 실행 파라미터 검증 가능

### Step 생성 (Chunk 기반 예시)

```java
@Bean
public Step simpleComposItemWriterStep() {
    return new StepBuilder("simpleComposItemWriterStep", jobRepository)
        .<SampleRequest, SampleRequest>chunk(CHUNK_SIZE, transactionManager)
        .reader(simpleItemReader())
        // .processor(...)
        .writer(simpleCompositeItemWriter())
        .build();
}
```

* reader: 데이터를 조회
* processor: 필요시 데이터 가공(선택)
* writer: 처리된 데이터를 저장(Insert/Update/Send 등)
* chunk 및 transactionManager를 통해 트랜잭션 단위를 지정

Chunk 기반의 reader/processor/writer 예시:

```java
@Bean
@StepScope
public ItemReader<SampleRequest> simpleItemReader() {
    return new ListItemReader<>(batSampleCompositeService.reader());
}

@Bean
public ItemWriter<SampleRequest> simpleCompositeItemWriter() {
    CompositeItemWriter<SampleRequest> compositeItemWriter = new CompositeItemWriter<>();
    compositeItemWriter.setDelegates(Arrays.asList(simpleUpdateService() /*, sampleUpdateService2() */));
    return compositeItemWriter;
}

@Bean
public ItemWriter<SampleRequest> simpleUpdateService() {
    return sampleList -> sampleList.forEach(batSampleCompositeService::writer2);
}
```

Tasklet 기반의 Step 작성 예시:

```java
@Bean
@JobScope
public Step sampleParamStep() {
    return new StepBuilder("sampleParamStep", jobRepository)
        .start(sampleParamTasklet(null))
        .incrementer(new UniqueRunIdIncrementer())
        .build();
}

@Bean
@JobScope
public SampleParamTasklet sampleParamTasklet(@Value("#{jobParameters[sampleParam]}") String sampleParam) {
    return new SampleParamTasklet(sampleParam);
}
```

Tasklet 구현 예시:

```java
public class SampleParamTasklet implements Tasklet {
    @Autowired
    private SampleService sampleService;
    private String sampleParam;

    public SampleParamTasklet(String sampleParam) {
        this.sampleParam = sampleParam;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        List<Sample> list = sampleService.getSampleList(new Sample());
        for (Sample sample : list) {
            Log.info("!!!!!! executed tasklet !!!!!!: {}, sampleParam: {}", sample, sampleParam);
        }
        return RepeatStatus.FINISHED;
    }
}
```

***

## 샘플 프로그램 목록

배치 프로젝트(gddp)의 샘플 패키지에 포함된 샘플 목록과 설명:

| 배치프로그램명                   | 파일                                  | 설명                                                                        |
| ------------------------- | ----------------------------------- | ------------------------------------------------------------------------- |
| simpleComposItemWriterJob | SimpleJobConfig.java                | CompositeItemWriter를 사용한 reader/writer 2단계 구성, processor 생략 샘플            |
| sampleFileJob             | SampleFileJobConfig.java            | FlatFileItemReader를 이용한 CSV 파일 읽기 샘플                                      |
| sampleCompositeWriterJob  | SampleCompositeWriterJobConfig.java | CompositeItemWriter로 Insert/Update/Send 등의 복합 작업 처리 샘플                    |
| sampleMyBatisCursorJob    | SampleMyBatisCursorJobConfig.java   | MyBatis Cursor Reader와 MyBatis Batch Writer 사용 샘플 (ParameterConverter 포함) |
| sampleJdbcJob             | SampleJdbcJob.java                  | JdbcCursorItemReaderBuilder와 JdbcBatchItemWriterBuilder 사용 샘플             |
| sampleJdbcPagingJob       | SampleJdbcPagingConfig.java         | JdbcPagingItemReaderBuilder 사용 샘플                                         |

간단한 일부 샘플 설명:

simpleComposItemWriterJob

* CompositeItemWriter를 사용하여 여러 writer를 Delegates로 구성.

sampleFileJob

* FlatFileItemReader로 CSV 파일을 읽음.

```java
@Bean
@StepScope
public FlatFileItemReader<SampleFileRequest> sampleFileReader() {
    String[] names = new String[] {"name", "description"};
    return new FlatFileItemReaderBuilder<SampleFileRequest>()
        .name("sampleFileRequest")
        .resource(new ClassPathResource("/csv/sample_data.csv"))
        .linesToSkip(1)
        .targetType(SampleFileRequest.class)
        .delimited().delimiter(",")
        .names(names)
        .build();
}
```

sampleCompositeWriterJob

* Processor를 포함하여 Input 타입(SampleRequest) → Output 타입(SampleResponse)으로 변환하는 샘플.

sampleMyBatisCursorJob

* MyBatis Cursor Reader와 MyBatis Batch Writer 사용 예시:

```java
@Bean
public MyBatisCursorItemReader<SampleRequest> sampleMyBatisCursorItemReader() {
    Map<String, Object> parameterValues = new HashMap<>();
    return new MyBatisCursorItemReaderBuilder<SampleRequest>()
        .sqlSessionFactory(displayRodbSqlSessionFactory)
        .queryId("com.x2bee.batch.gddp.app.repository.displayrodb.sample.BatSampleMapper.selectSampleList")
        .parameterValues(parameterValues)
        .build();
}

@Bean
public ItemWriter<SampleRequest> sampleMyBatisBatchItemWriter() {
    return new MyBatisBatchItemWriterBuilder<SampleRequest>()
        .sqlSessionFactory(displayRwdbSqlSessionFactory)
        .assertUpdates(false)
        .itemToParameterConverter(item -> {
            Map<String, Object> parameter = new HashMap<>();
            parameter.put("sysModrId", "BATCH");
            parameter.put("name", item.getName());
            return parameter;
        })
        .statementId("com.x2bee.batch.gddp.app.repository.displayrwdb.sample.BatSampleTrxMapper.updateSample")
        .build();
}
```

sampleJdbcJob (JdbcCursor, JdbcBatch 예시)

* JdbcCursorItemReaderBuilder 및 JdbcBatchItemWriterBuilder 사용. BoundSql에서 SQL을 가져와 PreparedStatementSetter 설정 등.

sampleJdbcPagingJob

* JdbcPagingItemReaderBuilder와 SqlPagingQueryProviderFactoryBean을 이용한 페이징 처리 예시.

***

## Spring Boot Batch 성능을 최적화하기 위한 Reader

Reader는 데이터를 효율적으로 읽어오고 처리 속도를 최적화하도록 구성해야 합니다. 아래는 주요 Reader 및 특징입니다.

* JdbcPagingItemReader
  * 페이지별로 데이터를 읽어 메모리 사용 효율적
  * 뒤로 갈수록 성능 저하 발생 가능
* JdbcCursorItemReader
  * 커서 기반으로 필요 시마다 데이터를 읽음
  * 메모리 효율적이며 대량 데이터 처리에 적합
* MyBatisPagingItemReader
  * MyBatis를 사용한 페이징 Reader
* MyBatisCursorItemReader
  * MyBatis와 커서 기반 통합 Reader

### 커스텀 Reader: QuerydslNoOffsetPagingItemReader

* QueryDSL을 사용해 offset 없이 페이징(Zero-Offset) 처리
* 장점: offset 성능 이슈 해결, 빠른 페이징
* 단점: Spring Batch에서 공식 지원하지 않음, 복잡한 쿼리에는 비권장

예시(요약):

```java
@Bean
@StepScope
public QuerydslNoOffsetPagingItemReader<QuerydslEntity> sampleQuerydslNoOffsetPaginItemReader(@Value("#{jobParameters[strDtm]}") String strDtm) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    LocalDateTime dateTime = LocalDateTime.parse(strDtm, formatter);

    QuerydslNoOffsetStringOptions<QuerydslEntity> options =
        new QuerydslNoOffsetStringOptions<>(querydslEntity.mbrNo, QuerydslReaderExpression.ASC);

    return new QuerydslNoOffsetPagingItemReader<>(entityManagerFactory, CHUNK_SIZE, options, queryFactory ->
        queryFactory.selectFrom(querydslEntity)
            .where(querydslEntity.mbrJoinDtm.after(dateTime))
    );
}
```

***

## 실제 예제와 테스트 (요약)

테스트 대상:

* 데이터 총 건수: 200k, 700k, 1,000k 건
* Chunk 크기: 1000 (모든 테스트에서 동일)
* 테스트 환경: 12th Gen Intel Core i7-1260P / LPDDR5 16GB
* 모든 ItemWriter는 동일하게 MyBatisBatchItemWriter 혹은 JdbcBatchItemWriter 사용

테스트 결과 요약(문서의 상세 표를 참조):

* Tasklet 기반: 가장 빠른 성능을 보였으나 메모리 사용량이 가장 높음(OOM 가능성)
* Chunk 기반: Tasklet보다 메모리 사용량 적음(Chunk 단위 트랜잭션)
* MyBatisCursor / JdbcCursor: 초기 Cursor Open 비용 존재, 이후 좋은 처리 성능
* JdbcPaging: 초반에는 빠르나 뒤로 갈수록 느려짐(Offset 증가 영향)
* QuerydslNoOffset: 테스트에서 가장 좋은 성능을 보였음(조건에 따라 유리)

(원문 문서에 상세한 성능 표가 포함되어 있습니다.)

***

## 최종결론

권장 조합(요약):

| Reader / Writer                                       | 내용                                                                                              |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| MyBatisCursorItemReader / MybatisBatchItemWriter      | 일반 환경에서는 MyBatis Cursor가 빠름. 저장은 MybatisBatchItemWriter로 Batch Insert/Update 권장.                |
| MyBatisPagingItemReader / MybatisBatchItemWriter      | 페이징을 항상 첫 페이지(offset 0)만 조회하도록 구성할 수 있다면 메모리 사용 적고 빠름(비즈니스 로직에 따라 유용).                          |
| QueryDslZeroOffsetItemReader / MybatisBatchItemWriter | 단일 테이블, PK가 시퀀스 형태인 경우 QueryDSL 기반 Zero-Offset Reader가 매우 우수한 성능을 보임. 단점은 쿼리를 QueryDSL로 변경해야 함. |


# Store Front Framewok (Next.js)

본 가이드는 Next.js(16.2.3), React.js(19.2.3) 버전을 기준으로 작성되었습니다.

### Next.js 는 무엇인가요? <a href="#next.js" id="next.js"></a>

Next.js는 풀스택 웹 애플리케이션을 구축하기 위한 React 프레임워크입니다. React 구성 요소를 사용하여 사용자 인터페이스를 구축하고 Next.js를 사용하여 추가 기능과 최적화를 수행합니다.

내부적으로 Next.js는 번들링, 컴파일 등과 같이 React에 필요한 도구를 추상화하고 자동으로 구성합니다. 이를 통해 구성에 시간을 낭비하는 대신 애플리케이션 구축에 집중할 수 있습니다.

개인 개발자이든 대규모 팀의 일원이든 Next.js는 대화형의 동적이며 빠른 React 애플리케이션을 구축하는 데 도움을 줄 수 있습니다.

### 주요 특징 <a href="#undefined" id="undefined"></a>

<table><thead><tr><th width="126.2222900390625">Feature</th><th>Description</th></tr></thead><tbody><tr><td>라우팅</td><td>레이아웃, 중첩 라우팅, 로딩 상태, 오류 처리 등을 지원하는 서버 구성 요소 위에 구축된 파일 시스템 기반 라우터입니다.</td></tr><tr><td>렌더링</td><td>클라이언트 및 서버 구성 요소를 사용한 클라이언트 측 및 서버 측 렌더링. Next.js를 사용하여 서버에서 정적 및 동적 렌더링으로 더욱 최적화되었습니다. Edge 및 Node.js 런타임에서 스트리밍합니다.</td></tr><tr><td>데이터 페칭</td><td>서버 구성 요소의 async/await를 사용하여 데이터 가져오기를 단순화하고 요청 메모, 데이터 캐싱 및 유효성 재검사를 위한 확장된 가져오기 API를 제공합니다.</td></tr><tr><td>스타일링</td><td>CSS 모듈, Tailwind CSS, CSS-in-JS 등 선호하는 스타일 지정 방법 지원</td></tr><tr><td>최적화</td><td>애플리케이션의 핵심 웹 바이탈 및 사용자 경험을 개선하기 위한 이미지, 글꼴 및 스크립트 최적화.</td></tr><tr><td>타입스크립트</td><td>더 나은 유형 검사, 더 효율적인 컴파일, 사용자 정의 TypeScript 플러그인 및 유형 검사기를 통해 TypeScript에 대한 지원이 향상되었습니다.</td></tr></tbody></table>

### App Router 와 Pages Router 비교 <a href="#app-router-pages-router" id="app-router-pages-router"></a>

Next.js에는 앱 라우터(App Router)와 페이지 라우터(Pages Router)라는 두 가지 라우터가 있습니다. 앱 라우터는 서버 컴포넌트 및 스트리밍과 같은 React의 최신 기능을 사용할 수 있는 최신 라우터입니다. Pages Router는 서버에서 렌더링된 React 애플리케이션을 구축할 수 있게 해 주고 이전 Next.js 애플리케이션에 대해 계속 지원되는 최초의 Next.js 라우터입니다.

사이드바 상단에는 앱 라우터와 페이지 라우터 기능 간에 전환할 수 있는 드롭다운 메뉴가 있습니다. 각 디렉터리마다 고유한 기능이 있으므로 어떤 탭이 선택되었는지 추적하는 것이 중요합니다.

페이지 상단의 이동 경로는 앱 라우터 문서를 보고 있는지 페이지 라우터 문서를 보고 있는지도 나타냅니다.

### 이 문서를 이용하는 방법 <a href="#undefined" id="undefined"></a>

이 문서는 바로 Next.js 프로젝트를 구성하여 실습할 수 있도록 주요 구성요소를 쉬운 순에서 어려운 순으로 단계적으로 구성하였습니다. 처음 설치부터, 데이터 페칭, 상태관리를 마치면 기본적으로 Next.js/React 를 활용한 웹 어플리케이션을 구성할 수 있습니다.

이문서는 계속 업데이트 됩니다. 그래서 독자분들의 피드백이 중요합니다. 페이지 마다 댓글을 통해 소중한 의견을 남겨 주세요


# 01. Setup

이 문서에서는 프로젝트 생성부터 프로젝트 구성 요소들을 비어 있는 환경에서부터 만들어 가는 작업을 하게 됩니다.

이 장이 끝나면 기본 디렉토리 구조 및 환경 구성 파일이 만들어 지게 됩니다.

&#x20;

Next.js 14 주요 업데이트 4가지 중 하나는 공식문서에서 교육과정을 제공해준다는 것입니다.

Next.js framework의 이해는 공식문서 Learn Next.js (번역-feat: 테크팀)도 참조 부탁드립니다.

링크 : [Learn Next.js](https://x2bee.tistory.com/category/Next.js%20%EA%B0%9C%EB%B0%9C%20%EA%B0%80%EC%9D%B4%EB%93%9C/06.%20Learn%20Next.js%20%EA%B3%B5%EC%8B%9D%20%EA%B0%80%EC%9D%B4%EB%93%9C?page=3)


# 1. 폴더 구조

이 문서는 Nextjs를 사용하여 개발된 프로젝트의 폴더 구조에 대한 설명입니다.

각 폴더의 구성의 다양한 역할을 설명하며, 이를 통해 프로젝트의 구조에 대해 이해할 수 있습니다.

***

최상위 폴더는 `src`와 `public`로 구성되어 있습니다. 아래는 각 폴더와 파일의 상세 설명입니다.

| /x2bee-storefront    |                                                           |                                        |                                              |                  |                      |
| -------------------- | --------------------------------------------------------- | -------------------------------------- | -------------------------------------------- | ---------------- | -------------------- |
| src                  |                                                           |                                        |                                              |                  |                      |
|                      | api                                                       | 비즈니스 API 요청을 작성                        |                                              |                  |                      |
|                      |                                                           | claim                                  | 반품, 환불처리 api                                 |                  |                      |
|                      |                                                           | common                                 | 공통 api                                       |                  |                      |
|                      |                                                           | customer                               | 고객 api                                       |                  |                      |
|                      |                                                           | delivery                               | 배송 api                                       |                  |                      |
|                      |                                                           | display                                | 전시 api                                       |                  |                      |
|                      |                                                           | event                                  | 이벤트 api                                      |                  |                      |
|                      |                                                           | goods                                  | 상품 api                                       |                  |                      |
|                      |                                                           | member                                 | 회원 api                                       |                  |                      |
|                      |                                                           | mypage                                 | 로그인사용자정보 api                                 |                  |                      |
|                      |                                                           | order                                  | 주문 api                                       |                  |                      |
|                      |                                                           | search                                 | 검색 api                                       |                  |                      |
|                      | app                                                       | 앱 라우터의 폴더 기반 라우팅                       |                                              |                  |                      |
|                      |                                                           | (common)                               | App router의 공통 기능 폴더                         |                  |                      |
|                      |                                                           |                                        | actions                                      | server action 폴더 |                      |
|                      |                                                           |                                        |                                              | serverCookie.ts  | server cookie action |
|                      |                                                           | (locale)                               | App router의 folder-based routing(다국어 적용)     |                  |                      |
|                      |                                                           |                                        | (root)                                       | root 폴더          |                      |
|                      |                                                           |                                        |                                              | layout.tsx       | common layout        |
|                      |                                                           |                                        |                                              | (home)           | Main Page            |
|                      |                                                           |                                        |                                              | claim            | 반품, 환불 Page          |
|                      |                                                           |                                        |                                              | common           | 공통 Page              |
|                      |                                                           |                                        |                                              | customer         | 고객 Page              |
|                      |                                                           |                                        |                                              | display          | 전시 Page              |
|                      |                                                           |                                        |                                              | event            | 이벤트 Page             |
|                      |                                                           |                                        |                                              | login            | 로그인 Page             |
|                      |                                                           |                                        |                                              | member           | 회원 Page              |
|                      |                                                           |                                        |                                              | order            | 주문 Page              |
|                      |                                                           |                                        | layout.tsx                                   | root layout      |                      |
|                      |                                                           |                                        | error.tsx                                    | root error Page  |                      |
|                      |                                                           | \[…not\_found]                         | 페이지 없음 폴더                                    |                  |                      |
|                      |                                                           |                                        | layout.tsx                                   | 페이지 없음 Page      |                      |
|                      |                                                           | goods                                  | 상품 Page                                      |                  |                      |
|                      |                                                           |                                        | layout.tsx                                   | 상품 Page layout   |                      |
|                      |                                                           |                                        |                                              | detail           | 상품 상세 Page           |
|                      |                                                           | payment                                | 결제 Page                                      |                  |                      |
|                      |                                                           |                                        | layout.tsx                                   | 상품 상세 layout     |                      |
|                      |                                                           | search                                 | 검색 Page                                      |                  |                      |
|                      | assets                                                    | css                                    |                                              |                  |                      |
|                      | constants                                                 | 상수값 정의                                 |                                              |                  |                      |
|                      | components                                                | app 폴더와 관련된 각각의 재사용 가능한 component를 정의함 |                                              |                  |                      |
|                      |                                                           | claim                                  | 반품, 환불 component                             |                  |                      |
|                      |                                                           | common                                 | 공통 component                                 |                  |                      |
|                      |                                                           | customer                               | 고객 component                                 |                  |                      |
|                      |                                                           | delivery                               | 배송 component                                 |                  |                      |
|                      |                                                           | display                                | 전시 component                                 |                  |                      |
|                      |                                                           | event                                  | 이벤트 component                                |                  |                      |
|                      |                                                           | fo                                     | fo component                                 |                  |                      |
|                      |                                                           | goods                                  | 상품 component                                 |                  |                      |
|                      |                                                           | member                                 | 회원 component                                 |                  |                      |
|                      |                                                           | order                                  | 주문 component                                 |                  |                      |
|                      |                                                           | ui                                     | ui component (button, calender, accordion 등) |                  |                      |
|                      | data                                                      | app에서 사용할 공통 데이터                       |                                              |                  |                      |
|                      |                                                           | i18n                                   | 다국어 데이터                                      |                  |                      |
|                      | hooks                                                     | custom hooks                           |                                              |                  |                      |
|                      | lib                                                       | plugins, 각종 library                    |                                              |                  |                      |
|                      | models                                                    | 업무별 ts model을 관리                       |                                              |                  |                      |
|                      | store                                                     | Zustand state를 관리                      |                                              |                  |                      |
|                      | types                                                     | Typescript를 위해 type 선언용 폴더             |                                              |                  |                      |
|                      | utils                                                     | util                                   |                                              |                  |                      |
|                      | i18n.ts                                                   | 다국어 설정 파일                              |                                              |                  |                      |
|                      | middleware.ts                                             | 미들웨어 파일                                |                                              |                  |                      |
|                      | navigation.ts                                             | 다국어 navigation 설정 파일                   |                                              |                  |                      |
| public               | 이미지등 public 폴더                                            |                                        |                                              |                  |                      |
|                      | images                                                    | image 파일 (svg파일과 icon 파일 포함)           |                                              |                  |                      |
| .env.development.set | <p>npm build:dev, start:dev<br>(development 환경 설정 파일)</p> |                                        |                                              |                  |                      |
| .env.local.set       | npm start:local (local 환경 설정 파일)                          |                                        |                                              |                  |                      |
| .env.production.set  | <p>npm build:prd, start:prd<br>(production 환경 설정 파일)</p>  |                                        |                                              |                  |                      |
| .env.stage.local     | <p>npm build:stg, start:stg<br>(stage 환경 설정 파일)</p>       |                                        |                                              |                  |                      |
| .eslintrc.json       | ESLint 설정 및 ESLint에 제외되는 규칙 설정                            |                                        |                                              |                  |                      |
| .gitignore           | git commit에서 제외되는 파일 목록                                   |                                        |                                              |                  |                      |
| .pretterrc           | ESLint와 Prettier 충돌 방지용 Prettier 설정                       |                                        |                                              |                  |                      |
| next-env.d.ts        | Next.js의 Typescript 선언용 파일                                |                                        |                                              |                  |                      |
| next.config.js       | Next.js config 설정                                         |                                        |                                              |                  |                      |
| package.json         | 각종 패키지 / dependency 정보                                    |                                        |                                              |                  |                      |
| README.md            | gitlab용 readme 파일                                         |                                        |                                              |                  |                      |
| tailwind.config.ts   | tailwind css를 위한 config                                   |                                        |                                              |                  |                      |

* /x2bee-storefront
  * src
    * api — 비즈니스 API 요청을 작성
      * claim — 반품, 환불처리 api
      * common — 공통 api
      * customer — 고객 api
      * delivery — 배송 api
      * display — 전시 api
      * event — 이벤트 api
      * goods — 상품 api
      * member — 회원 api
      * mypage — 로그인 사용자 정보 api
      * order — 주문 api
      * search — 검색 api
    * app — 앱 라우터의 폴더 기반 라우팅
      * (common) — App router의 공통 기능 폴더
        * actions — server action 폴더
          * `serverCookie.ts` — server cookie action
      * (locale) — App router의 folder-based routing(다국어 적용)
        * (root) — root 폴더
          * `layout.tsx` — common layout
          * (home) — Main Page
          * claim — 반품, 환불 Page
          * common — 공통 Page
          * customer — 고객 Page
          * display — 전시 Page
          * event — 이벤트 Page
          * login — 로그인 Page
          * member — 회원 Page
          * order — 주문 Page
        * `layout.tsx` — root layout
        * `error.tsx` — root error Page
        * \[…not\_found] — 페이지 없음 폴더
          * `layout.tsx` — 페이지 없음 Page
      * goods — 상품 Page
        * `layout.tsx` — 상품 Page layout
        * detail — 상품 상세 Page
      * payment — 결제 Page
        * `layout.tsx` — 상품 상세 layout
      * search — 검색 Page
    * assets
      * css
    * constants — 상수값 정의
    * components — app 폴더와 관련된 각각의 재사용 가능한 component를 정의함
      * claim — 반품, 환불 component
      * common — 공통 component
      * customer — 고객 component
      * delivery — 배송 component
      * display — 전시 component
      * event — 이벤트 component
      * fo — fo component
      * goods — 상품 component
      * member — 회원 component
      * order — 주문 component
      * ui — ui component (button, calender, accordion 등)
    * data — app에서 사용할 공통 데이터
      * i18n — 다국어 데이터
    * hooks — custom hooks
    * lib — plugins, 각종 library
    * models — 업무별 ts model을 관리
    * store — Zustand state를 관리
    * types — Typescript를 위해 type 선언용 폴더
    * utils — util
    * `i18n.ts` — 다국어 설정 파일
    * `middleware.ts` — 미들웨어 파일
    * `navigation.ts` — 다국어 navigation 설정 파일
  * public — 이미지 등 public 폴더
    * images — image 파일 (svg파일과 icon 파일 포함)
  * 환경 설정 파일
    * `.env.development.set` — npm build:dev, start:dev (development 환경 설정 파일)
    * `.env.local.set` — npm start:local (local 환경 설정 파일)
    * `.env.production.set` — npm build:prd, start:prd (production 환경 설정 파일)
    * `.env.stage.local` — npm build:stg, start:stg (stage 환경 설정 파일)
  * 기타 설정 파일
    * `.eslintrc.json` — ESLint 설정 및 ESLint에 제외되는 규칙 설정
    * `.gitignore` — git commit에서 제외되는 파일 목록
    * `.pretterrc` — ESLint와 Prettier 충돌 방지용 Prettier 설정
    * `next-env.d.ts` — Next.js의 Typescript 선언용 파일
    * `next.config.js` — Next.js config 설정
    * `package.json` — 각종 패키지 / dependency 정보
    * `README.md` — gitlab용 readme 파일
    * `tailwind.config.ts` — tailwind css를 위한 config

Document generated by Confluence on 2025-12-18 2:13 오전


# 2. Create app

이 문서는 Next.js를 사용하여 프로젝트를 시작하는 방법을 안내합니다. 프로젝트를 시작하기 위해 필요한 설정 내용과 방법을 단계별로 설명하며, 최종적으로 Next.js 앱을 로컬 환경에서 실행합니다.

***

{% hint style="info" %}
Prerequisites

node: 현재 기준 최신 Next.js 16.2.3은 Node.js 20 이상만 지원하므로 버전 24 이상 설치를 추천합니다.

(현재 LTS 최신 버전은 24.16.0)
{% endhint %}

## Set up

{% stepper %}
{% step %}

### 프로젝트 폴더 생성 및 IDE 열기

예: app 이름 : x2bee-fo-dev

```bash
$ mkdir x2bee-fo
$ cd x2bee-fo
$ code .  # 자신이 쓰는 IDE 열기
```

{% endstep %}

{% step %}

### package.json 생성

프로젝트 루트에 package.json 파일을 생성하고 다음 내용을 작성합니다.

```json
{
  "name": "x2bee-fo",
  "private": true
}
```

{% endstep %}

{% step %}

### 의존성 설치

Next.js, React, React DOM을 설치합니다.

( 만약 npm이나 yarn을 쓰는 경우 install, bun을 쓰는 경우 bun add 하면 됩니다. )

<pre><code><strong>$ yarn add next react react-dom
</strong></code></pre>

{% endstep %}
{% endstepper %}

설치가 완료되면 node\_modules 폴더가 생성되고 해당 경로에 패키지들이 설치됩니다.

## git 설정

Git을 사용하여 프로젝트를 관리할 때 무시해야 할 파일 및 디렉토리를 설정하는 .gitignore 파일을 생성합니다.

**.gitignore** 파일 내용:

```gitignore
# next.js
/.next/

# dependencies
/node_modules
pnpm-lock.yaml
package-lock.json
yarn.lock
```

이러한 항목을 .gitignore에 추가하면 Git 저장소에 불필요한 파일이나 의존성 패키지들이 포함되지 않도록 할 수 있습니다.

## Scripts 및 초기 실행

package.json에 scripts를 추가합니다. 예시:

```json
{
  "name": "x2bee-fo",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
"next": "^16.2.3",
"react": "^19.2.3",
"react-dom": "^19.2.3"
  }
}
```

이제 개발 서버를 실행합니다:

```bash
$ npm run dev
```

(또는 yarn: `yarn dev`, pnpm: `pnpm dev`)

실행 시 .next 폴더가 자동으로 생성되며 다음과 같은 메시지를 볼 수 있습니다:

* Local: <http://localhost:3000>
* Ready in Xs

(아직 page.jsx 파일이 없으므로 브라우저에서는 404 에러가 뜰 수 있습니다.)

## 프로젝트 초기화 및 원격 저장소 연결

다음 명령으로 Git 저장소를 초기화하고 원격 저장소를 연결합니다.

```bash
$ git init
$ git remote add origin https:// git 주소
$ git remote -v  # 확인용
```

변경사항을 커밋하고 푸시:

```bash
$ git add .
$ git commit -m 'x2bee storefront with Next14'
$ git push origin main
```

## 기본 레이아웃 생성

프로젝트 루트에 src 폴더를 만들고, 그 안에 app 폴더를 만든 후 layout.jsx 파일을 생성하고 다음 코드를 복사하여 붙여넣습니다.

```javascript
import React from 'react'

const RootLayout = ({ children }) => {
  return (
    <html lang="ko">
      <body>{children}</body>
    </html>
  );
};

export default RootLayout;
```

수행이 완료되면 기본 루트 레이아웃 정의가 완료됩니다.

## 참고

```bash
$ ls node_modules/.bin  # next 등이 있는지 확인
# next
```

npx로 next를 실행하면 위 경로가 사용됩니다.

```bash
$ npx next --help
# Available commands build, start, export, dev, lint, telemetry, info, experimental-compile, experimental-generate
```

위 명령들을 활용하여 package.json의 scripts를 구성할 수 있습니다.

***


# 3. Typescript

이 문서는 TypeScript를 사용하여 Next.js 프로젝트를 설정하고 실행하는 방법에 대한 안내입니다.

먼저 프로젝트의 기본적인 설정을 위해 파일 이름과 확장자를 변경하고, 이에 따른 종속성을 자동으로 추가하는 과정을 설명합니다.

***

### Typescript 설정

{% stepper %}
{% step %}

### layout 파일 확장자 변경 및 개발 서버 실행

src/app/layout.jsx 파일의 이름을 .tsx로 변경하고 개발 서버를 실행합니다.

```bash
$ npm run dev
```

파일의 이름을 .tsx로 수정하면 TypeScript 파일로 인식됩니다. TypeScript 파일은 TypeScript와 JSX를 함께 사용할 때 사용됩니다.

개발 서버를 실행하면 package.json에 자동으로 다음 devDependencies가 추가됩니다.

```json
"devDependencies": {
  "@types/node": "20.12.5",
  "@types/react": "18.2.74",
  "typescript": "5.3.3"
}
```

또한 프로젝트 루트에 `next-env.d.ts`와 `tsconfig.json` 파일이 생성됩니다. 이 두 파일은 기본으로 생성되는 파일이므로 수정할 필요는 없습니다.
{% endstep %}

{% step %}

### layout.tsx 코드 변경

확장자만 `.tsx`로 변경했으므로, 타입을 반영한 코드로 수정합니다.

{% code title="src/app/layout.tsx" %}

```tsx
import React from "react";
import type { ReactNode } from "react";

interface LayoutProps {
  children: ReactNode;
}

const RootLayout = ({ children }: LayoutProps) => {
  return (
    <html lang="ko">
      <body>
        <header>header</header>
        <main>{children}</main>
        <footer>footer</footer>
      </body>
    </html>
  );
};

export default RootLayout;
```

{% endcode %}

참고: 첫번째 줄 `import React from 'react';`는 생략해도 됩니다. (React 17부터 생략 가능. 현재는 React 18 사용 중)
{% endstep %}

{% step %}

### Main page 생성 (App Router)

App Router를 사용하므로 `src/app/page.tsx` 파일을 생성하고 다음 코드를 입력합니다.

{% code title="src/app/page.tsx" %}

```tsx
const Home = () => {
  return (
    <div>
      <h1>Home</h1>
    </div>
  );
};
export default Home;
```

{% endcode %}
{% endstep %}

{% step %}

### 개발 서버 실행 및 확인

다시 개발 서버를 실행합니다.

```bash
$ npm run dev
```

실행 후 localhost:3000으로 접속하면 페이지가 정상적으로 표시되는 것을 확인할 수 있습니다.

<figure><img src="/files/89d7Uxhr6NYiVaZSvcsy" alt=""><figcaption></figcaption></figure>

{% endstep %}
{% endstepper %}

<figure><img src="/files/woLkmNOk4FgFSh8Re7z3" alt=""><figcaption></figcaption></figure>

참고: App Router에서 페이지 파일명은 반드시 `page.tsx`이어야 합니다.


# 4. ESLint, Prettier

이 문서는 ESLint와 Prettier를 설정하여 JavaScript 또는 TypeScript 기반의 프로젝트에서 코드 품질을 향상시키는 방법에 대한 안내입니다. 먼저 ESLint와 Prettier를 설치하고 기본적인 설정을 추가하는 방법을 설명합니다. 이후 Tailwind CSS와의 충돌을 방지하기 위한 설정 및 예외 처리도 다룹니다. ※ 본 페이지는 일반적인 ESLint·Prettier 설정 흐름을 설명합니다. X2BEE FO/BO의 실제 구성은 .eslintrc.js(module.exports · parser @typescript-eslint/parser · extends: eslint:recommended, plugin:react/recommended, plugin:@typescript-eslint/recommended, plugin:prettier/recommended)와 .prettierrc(semi: false · singleQuote: true · trailingComma: es5 · prettier-plugin-tailwindcss)이며, 패키지 매니저는 yarn 입니다.

{% stepper %}
{% step %}

### 설치 — ESLint 기본 설치

pnpm 패키지 매니저를 사용하여 eslint와 eslint-config-next를 설치합니다.

{% code title="Install packages" %}

```bash
$ pnpm add eslint eslint-config-next
```

{% endcode %}

package.json에 lint 스크립트를 추가합니다.

{% code title="package.json (scripts)" %}

```json
"scripts": {
  ...
  "lint": "next lint --no-cache"
}
```

{% endcode %}

참고:

{% hint style="info" %}
\--no-cache는 생략 가능하며, 붙일 경우 변경사항과 상관없이 전체 파일을 다시 검사합니다.
{% endhint %}
{% endstep %}

{% step %}

### Standard package 설치

pnpm으로 eslint-config-standard를 설치합니다.

{% code title="Install standard config" %}

```bash
$ pnpm add eslint-config-standard
```

{% endcode %}

프로젝트 루트에 .eslintrc.json 파일을 생성하고 아래를 작성합니다.

{% code title=".eslintrc.json" %}

```json
{
  "extends": ["next/core-web-vitals", "standard"]
}
```

{% endcode %}

이 설정으로 ESLint가 프로젝트 코드를 분석하고 표준 규칙을 적용합니다.
{% endstep %}

{% step %}

### ESLint config — Tailwind CSS 검사 추가

pnpm으로 eslint-plugin-tailwindcss를 설치합니다.

{% code title="Install Tailwind ESLint plugin" %}

```bash
$ pnpm add eslint-plugin-tailwindcss
```

{% endcode %}

eslint-plugin-tailwindcss는 Tailwind 유틸리티 클래스의 유효성(올바른 클래스 사용 여부)과 충돌하는 속성(예: className="flex grid")을 검사해줍니다.

.eslintrc.json에 plugin:tailwindcss/recommended를 추가합니다:

{% code title=".eslintrc.json (with tailwind plugin)" %}

```json
{
  "extends": ["next/core-web-vitals", "standard", "plugin:tailwindcss/recommended"]
}
```

{% endcode %}
{% endstep %}

{% step %}

### Prettier 설정 및 Tailwind 정렬 플러그인

pnpm으로 prettier 관련 패키지를 설치합니다.

{% code title="Install Prettier and plugins" %}

```bash
$ pnpm add prettier eslint-config-prettier prettier-plugin-tailwindcss
```

{% endcode %}

eslint-config-prettier는 ESLint와 Prettier 간 중복 규칙을 비활성화하여 충돌을 방지합니다.

.eslintrc.json에 prettier와 예외 규칙을 추가합니다:

{% code title=".eslintrc.json (with prettier & rules)" %}

```json
{
  "extends": [
    "next/core-web-vitals",
    "standard",
    "plugin:tailwindcss/recommended",
    "prettier"
  ],
  "rules": {
    "no-undef": "off",
    "spaced-comment": "off",
    "tailwindcss/classnames-order": "off"
  }
}
```

{% endcode %}

rules 설명:

* no-undef: React 17+에서 `import React from 'react'` 생략 시 발생할 수 있는 경고 예외 처리
* spaced-comment: 주석 관련 lint 경고 예외 처리
* tailwindcss/classnames-order: 커스텀 className과 Tailwind 공식 순서 규칙 충돌로 인한 예외 처리

추가로 Prettier 설정 파일(.prettierrc)을 생성합니다:

```json
{
  "printWidth": 80,
  "tabWidth": 2,
"semi": false,
  "singleQuote": true,
  "useTabs": false,
  "endOfLine": "auto",
  "trailingComma": "es5",
  "arrowParens": "always",
  "bracketSpacing": true,
  "htmlWhitespaceSensitivity": "css",
  "jsxBracketSameLine": false,
  "jsxSingleQuote": false,
  "tsxSingleQuote": false,
  "proseWrap": "preserve",
  "quoteProps": "as-needed",
  "vueIndentScriptAndStyle": true,
  "requirePragma": false,
  "plugins": ["prettier-plugin-tailwindcss"]
}
```

{% endstep %}

{% step %}

### VS Code 확장 (권장)

다음 확장을 설치하면 편리합니다:

* ESLint
* Prettier - Code formatter
* Prettier ESLint
  {% endstep %}

{% step %}

### ESLint 검사 실행

프로젝트 루트에서 lint를 실행합니다:

```bash
$ npm run lint
```

```

위 명령은 프로젝트의 모든 파일을 ESLint 규칙에 따라 검사합니다.

</div>

</div>

추가 참고사항:
- Tailwind 관련 lint 규칙과 Prettier의 tailwind 정렬 플러그인을 함께 사용하면 클래스 순서와 형식 관련 일관성을 유지할 수 있습니다. 필요에 따라 .eslintrc.json의 규칙을 조정해 예외를 처리하세요.

문서 작성 시점의 메타정보(Confluence 생성/수정 시각 등)는 본 페이지에서 제거되었습니다.
```

{% endstep %}
{% endstepper %}


# 5. Tailwind CSS

이 문서는 Tailwind CSS를 사용하여 프로젝트에 리셋 CSS를 적용하는 방법에 대한 설명입니다. ※ X2BEE FO/BO는 Tailwind CSS v4(4.1.16)를 사용합니다. v4부터는 globals.css 에 @import "tailwindcss"; 한 줄이면 되고, tailwind.config 파일과 autoprefixer 가 필요 없으며(@tailwindcss/postcss 가 vendor prefix 를 처리), 테마는 globals.css 의 @theme 블록에서 설정합니다. 실제 FO globals 경로는 src/assets/styles/page/globals.css 입니다. 아래 설치 절차는 구 v3 기준이므로 참고용입니다.

***

## Reset CSS

1. `src/app/layout.tsx`에 다음 한 줄을 추가합니다.

{% code title="src/app/layout.tsx" %}

```typescript
import '@/app/ui/globals.css';
```

{% endcode %}

2. 파일을 생성하고 `src/app/ui/globals.css`에 다음 3줄을 입력합니다.

{% code title="src/app/ui/globals.css" %}

```css
@import "tailwindcss";
/* Tailwind v4: 위 한 줄로 base·components·utilities 포함 */
/* (v3 의 @tailwind 지시문은 v4 에서 불필요) */
```

{% endcode %}

{% hint style="warning" %}
위 설정을 추가하면 처음에 VS Code에서 warning이 뜰 수 있습니다.\
이 경우, VS Code에서 "PostCSS Language Support" 확장(extension)을 설치하면 warning이 사라집니다.
{% endhint %}

***

## Install

참조: <https://tailwindcss.com/docs/guides/nextjs\\>
참조: <https://nextjs.org/docs/app/building-your-application/styling/tailwind-css>

다음은 설치와 초기 설정 순서입니다:

{% stepper %}
{% step %}

### 설치: Tailwind 및 관련 패키지 추가

터미널에서 다음 명령을 실행하여 패키지 3개를 개발 의존성으로 설치합니다.

```bash
$ yarn add tailwindcss @tailwindcss/postcss
```

{% endstep %}

{% step %}

### 초기화: Tailwind 설정 파일 생성

다음 명령을 실행하면 `tailwind.config.js`와 `postcss.config.js` 파일 2개가 자동 생성됩니다.

```bash
$ npx tailwindcss init -p
```

{% endstep %}

{% step %}

### tailwind.config 파일을 TypeScript로 변경하고 내용 교체

생성된 `tailwind.config.js`의 이름을 `.ts`로 바꿔도 됩니다 (예: `tailwind.config.ts`). 파일 내용을 다음 코드로 대체하세요.

{% code title="tailwind.config.ts" %}

```ts
import type { Config } from 'tailwindcss';

const config: Config = {
  content: [
    './src/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

export default config;
```

{% endcode %}
{% endstep %}

{% step %}

### 개발 서버 실행

다음 명령으로 개발 서버를 실행하면 CSS가 리셋된 것을 확인할 수 있습니다.

```bash
$ npm run dev
```

{% endstep %}
{% endstepper %}

<figure><img src="/files/89d7Uxhr6NYiVaZSvcsy" alt=""><figcaption></figcaption></figure>

***

## VS Code 추가 extension

* Tailwind CSS IntelliSense: 코딩 시 utility class 목록과 자동완성 기능을 제공합니다.

{% hint style="info" %}
Tailwind 관련 VS Code 확장(예: Tailwind CSS IntelliSense, PostCSS Language Support)을 설치하면 개발 경험이 훨씬 좋아집니다.
{% endhint %}


# 6. 환경 및 Metadata

이 문서는 Nextjs 프로젝트에서 환경 변수 및 메타데이터 설정에 관한 안내입니다.\
먼저, 환경 변수 설정에 대해 설명하고 `next.config.js` 파일을 설정하는 방법을 설명합니다.\
아래의 내용을 통해서 환경 변수, 메타데이터, 파일 구조 및 설정을 알 수 있습니다.

***

## Environment variables

참조: <https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#environment-variable-load-order>

* 개발 환경: `.env.development.local` 파일을 생성하고 `npm run dev`로 실행합니다.
* 배포(프로덕션) 환경: `.env.production.local` 파일을 생성합니다.
* 코드에서 참조할 때는 `process.env` 형태로 접근합니다. 예: `process.env.API_URL`

{% stepper %}
{% step %}

### 환경 변수 파일 생성 (개발)

개발 실행 시 사용되는 파일을 프로젝트 루트에 생성합니다:

* .env.development.local

환경별로 필요한 키와 값을 추가합니다.
{% endstep %}

{% step %}

### 환경 변수 파일 생성 (프로덕션)

배포 환경에서 사용되는 파일을 프로젝트 루트에 생성합니다:

* .env.production.local

배포 전 해당 파일이 올바르게 설정되었는지 확인하세요.
{% endstep %}

{% step %}

### 코드에서 사용

코드 상에서 환경 변수를 참조할 때는 다음과 같이 사용합니다:

```javascript
process.env.API_URL
```

{% endstep %}
{% endstepper %}

***

## next.config.js

참고:

* <https://nextjs.org/docs/app/building-your-application/configuring/typescript#type-checking-nextconfigjs>
* <https://nextjs.org/docs/app/api-reference/next-config-js>

{% hint style="info" %}
`next.config.js` 파일은 Next.js 서버 빌드 시 참조되는 설정 파일입니다. 이 파일은 일반적인 Node 모듈이나 Babel/TS로 파싱되지 않으므로 `.ts` 확장자로 바꿀 수 없습니다. 반드시 `next.config.js`로 프로젝트 루트에 위치시켜야 합니다.
{% endhint %}

다음과 같이 루트에 `next.config.js` 파일을 만들고 설정을 추가합니다:

{% code title="next.config.js" %}

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  /* config options here */
}

module.exports = nextConfig
```

{% endcode %}

***

## favicon.ico, Title 및 Metadata

* 예전에는 `public` 폴더에 `favicon.ico`를 넣었지만, 현재는 `src/app` 폴더에 넣어도 됩니다.
* 요즘 트렌드는 브라우저 타이틀을 "소제목 | 홈페이지이름" 형태로 표시하는 것입니다.

예시로 `src/app/layout.tsx`에 다음을 추가합니다:

```typescript
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: {
    default: 'NEXT MALL',
    template: '%s | NEXT MALL',
  },
  description: 'X2BEE MALL FO by Plateer',
  icons: {
    icon: '/favicon.ico',
  },
};
```

* 이 설정이 있으면 각 `page.tsx`에 title metadata가 없을 때 기본값으로 `NEXT MALL`이 표시됩니다.
* `page.tsx`에서 소제목(title)을 별도로 설정하면 `소 제목 | NEXT MALL` 형태로 표시됩니다.

### Subfolder의 Metadata 예시

```typescript
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: "My Page",
  description: 'X2BEE MALL FO by Plateer',
};
```

위와 같이 설정하면 브라우저 타이틀에 `My Page | NEXT MALL`로 나타납니다.

***

## Naming conventions

Nuxt와는 반대로, 다음 컨벤션을 권장합니다.

* 폴더(파일 기반 라우팅): kebab-case 사용 (URL 친화적)
  * BAD: `myComponent/page.tsx`, `Mycomponent/page.tsx`
  * GOOD: `my-component/page.tsx`

참고: Vercel 공식 GitHub 예제에서는 파일명이 전부 kebab-case입니다. `src/components` 폴더 내부 일부가 camelCase인 경우도 있으나, 통일성을 위해 X2bee는 kebab-case로 통일합니다.

* Component 이름: PascalCase 사용
  * 예: `export default const MyComponent = () => { ... }`
* 컴포넌트의 파일/폴더 이름과 컴포넌트 내부 이름은 달라도 동작에는 문제가 없지만, 가독성과 디버깅을 위해 가능하면 일치시키는 것을 권장합니다.

***

## Import alias

tsconfig.json에서 경로 별칭을 설정하여 import를 간편하게 할 수 있습니다.

{% tabs %}
{% tab title="프로젝트에 src 폴더가 있을 때" %}
tsconfig.json 예시:

```json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
  // ...
}
```

{% endtab %}

{% tab title="프로젝트에 src 폴더가 없을 때" %}
tsconfig.json 예시:

```json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./*"]
    }
  }
  // ...
}
```

{% endtab %}
{% endtabs %}

***


# 02. 코딩 가이드 및 필수 팩키지

이번 장에서는 기본 코딩 컨벤션 및 설치해야 할 필 수 패키지를 구성하는 방법에 대해서 안내합니다.


# 1. 코딩 스타일 가이드

이 문서는 Nextjs 프로젝트 개발 시 코딩 스타일 작성 방법을 설명합니다.

{% stepper %}
{% step %}

### Componentization

{% hint style="info" %}
가장 중요함: 코딩 후에 refactor도 필요하지만, 우선 처음부터 component화 시키는 것이 중요합니다.
{% endhint %}

가독성과 코드 재사용을 위해서 반드시 component화가 필요합니다. Composable commerce의 필수 요건이라 할 수 있습니다.

예시: layout.tsx

{% code title="layout.tsx" %}

```
```

{% endcode %}

```tsx
const Layout = ({ children }) => (
  <div className="layout">
    <header> Header content ... ... </header>
    <main>{children}</main>
    <footer> Footer content ... ... </footer>
  </div>
);
export default Layout;
```

이 파일 하나에 수 천 line 코딩하지 말고, Header와 Footer를 src/components/에 코딩 후 import 합니다.

{% code title="layout.tsx (refactored)" %}

```tsx
import Header from '@/components/header';
import Footer from '@/components/footer';

const Layout = ({ children }) => (
  <div className="layout">
    <Header />
    <main>{children}</main>
    <Footer />
  </div>
);
export default Layout;
```

{% endcode %}
{% endstep %}

{% step %}

### Nullish Coalescing

물음표 두 개 ??를 사용하는 문법입니다. ES2020에 도입된 JavaScript 문법이며, REST API의 response를 json으로 받아 체크할 때 유용합니다.

예시 설명:

* false || true; // => true
* false ?? true; // => false

false라는 값은 boolean 이고, 기본값 연산자 ??는 **undefined 혹은 null** 일 때만 뒤의 default 값을 사용합니다. 즉 응답 json에서 의도적인 빈 문자열 "" 값을 받을 때 이를 변형하지 않습니다.

사용 예시:

{% code title="예시" %}

```javascript
const name = foo ?? "default value";
```

{% endcode %}
{% endstep %}

{% step %}

### 고차함수

.map, .filter, .reduce의 문법을 익히면 좋습니다.

예시:

{% code title="React (map) 예시" %}

```jsx
return (
  <ul>
    {items.map((item, index) => (
      <li key={index}>
        {item}
      </li>
    ))}
  </ul>
)
```

{% endcode %}
{% endstep %}

{% step %}

### key

Next.js 고유가 아닌 모든 React 기반으로 코딩할 때 .map()을 쓰면 항상 key 값을 주어야 합니다. 처음 React를 접할 때 가장 많이 하는 실수입니다. 위의 고차함수(map) 예시를 참고하세요.
{% endstep %}

{% step %}

### try-catch

예시:

{% code title="fetch 예시" %}

```javascript
async function fetchData() {
  // An async function for fetching data from an API
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`API call failed with status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    // Error handling or logging logic
    throw error;
  }
}
```

{% endcode %}
{% endstep %}

{% step %}

### ternary operator

예시:

{% code title="ternary 예시" %}

```jsx
return (
  <>
    {isLogin ? <div>Logged in</div> : <div>Not Logged in</div>}
  </>
)
```

{% endcode %}
{% endstep %}

{% step %}

### Destructuring

예시: without destructuring

{% code title="without destructuring" %}

```javascript
function displayPerson(person) {
  const name = person.name;
  console.log(name);
}
```

{% endcode %}

예시: with destructuring

{% code title="with destructuring" %}

```javascript
function displayPerson({ name, age, job }) {
  console.log(name);
}
```

{% endcode %}
{% endstep %}

{% step %}

### optional chaining

값이 없을 때 나는 오류를 방지하기 위해 required field가 아니면 물음표 입력:

예시:

```javascript
productList?.info?.color
```

{% endstep %}
{% endstepper %}


# 2. Json schema validator : Zod

이 문서는 REST API로부터 받은 JSON 데이터의 유효성을 검사하기 위한 필수 패키지인 Zod의 사용법을 안내합니다.

***

## 설치

Zod는 runtime dependency로 사용하므로 --save-dev가 아닌 바로 install 합니다.

{% hint style="info" %}
Zod는 런타임 의존성입니다. 개발 의존성(devDependency)으로 설치하지 마세요.
{% endhint %}

{% code title="설치" %}

```bash
pnpm add zod
```

{% endcode %}

## 사용

{% code title="예제: Product 유효성 검사 (React 컴포넌트)" %}

```javascript
import { z } from 'zod';

const productJson = {
  name: 'jeans',
  price: 100,
};

const productSchema = z.object({
  name: z.string(),
  price: z.number().positive(), // 양수, 음수 구별 가능
});

// type Product = z.infer<typeof productSchema>;

const Test1 = () => {
  const validateProduct = productSchema.safeParse(productJson);
  console.log('validateProduct', validateProduct);

  if (validateProduct.success === false) {
    console.error(validateProduct.error.message);
    return;
  }

  return <div>Test1</div>;
};

export default Test1;
```

{% endcode %}

{% stepper %}
{% step %}

### infer

위 코드는 예시를 위해 fetch를 통해 json을 가지고오지 않고 바로 하드코딩한 예입니다.

만약 fetch를 통해 가져온다면 미리 type(interface)을 지정해줘야 하는데, 이러면 validation에서 한 번 더 입력해줘야 하니 interface를 두 번 작업해야되는 번거로움이 생깁니다. 그래서 위에서처럼,

type Product = z.infer;

zod로 한 번 type을 검토하고, 위 코드처럼 한 줄로 TypeScript에서 type을 지정해줄 수 있습니다.
{% endstep %}

{% step %}

### safeParse

위 코드처럼 safeParse를 해주고 validateProduct를 출력하면,

validateProduct { success: true, data: { name: 'jeans', price: 100 } }

success라는 키가 true / false인지 알려줍니다.
{% endstep %}
{% endstepper %}

## 테스트

이제 위에 하드코딩된 Json의 name을 → id 로 변경해주면 콘솔 출력은 다음과 같이 됩니다:

validateProduct { success: false, error: \[Getter] }

ZodError 예시: { "code": "invalid\_type", "expected": "string", "received": "undefined", "path": \[ "name" ], "message": "Required" }

validateProduct.success는 false가 되고, Required 필드인 name이 invalid type이라고 표시됩니다.


# 3. Tailwind-merge + clsx

이 문서는 Composable commerce를 구현하는 데 필수적인 패키지인 tailwind-merge와 clsx의 설치와 사용법을 안내합니다.

***

## 설치

Composable commerce를 실현하는 가장 핵심적인 패키지입니다.

두 가지 패키지 설치합니다.

{% code title="설치 (pnpm)" %}

```bash
pnpm add tailwind-merge clsx
```

{% endcode %}

## 사용

src/lib/common/ui/utils.ts 폴더와 파일을 생성 (X2BEE 프로젝트 구조 기준) 하고, 다음 코드를 입력합니다.

{% code title="src/lib/common/ui/utils.ts" %}

```typescript
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
```

{% endcode %}

다른 component에서 사용시 import 합니다.

```typescript
import { cn } from "@/lib/common/ui/utils"
```

더 자세한 사용법은 03. 퍼블 가이드 - 3. Button variants를 참조합니다.


# 4. Internationalization와 i18n

이 문서는 Nextjs 프로젝트에서 다국어 사이트를 개발하는 방법에 대해 설명합니다.

미들웨어를 사용하여 언어를 추출하고, 다국어 콘텐츠를 관리하기 위해 JSON 파일을 관리, i18n 설정과 언어 변경 및 이동 방법을 설명합니다.

***

Next.js는 여러 언어를 지원하기 위해 라우팅 및 콘텐츠 렌더링을 구성할 수 있게 해줍니다. 여러 locale에 대응하는 사이트를 만들기 위해서는 번역된 콘텐츠(localization)와 internationalized routes가 포함됩니다.

## Middleware (/src/middleware.ts)

미들웨어는 클라이언트의 요청에서 locale 을 추출하고, 이에 따라 적절한 redirect 또는 rewrite를 수행합니다. 만약 클라이언트가 /about에 대한 요청을 하고 locale이 en으로 설정되어 있다면, 미들웨어는 이를 /en/about으로 redirect하거나 rewrite합니다.

이러한 처리를 통해 사용자는 적절한 언어로 번역된 페이지로 redirect되거나 rewrite되어 효과적으로 국제화된 콘텐츠를 제공받을 수 있습니다.

{% code title="/src/middleware.ts" %}

```ts
import createMiddleware from 'next-intl/middleware'
import { NextRequest } from 'next/server'
import { locales } from './navigation'

/**
 * 국제화 (i18n)
 * ko-KR : Korean (Korea)
 * en-US : English (United States)
 * zh-CN : Chinese (S)
 * zh-TW : Chinese (T)
 * ja-JP : Japanese (Japan)
 */
export default async function middleware(request: NextRequest) {
  const acceptLanguage = request.headers.get('accept-language') || process.env.DEFAULT_LOCALE

  const defaultLocales = locales.filter((locale) => {
    if (acceptLanguage.startsWith(locale)) return true
    else return false
  })

  const defaultLocale = defaultLocales[0] || process.env.DEFAULT_LOCALE

  const handleI18nRouting = createMiddleware({
    locales,
    defaultLocale,
    localePrefix: process.env.LOCALE_PREFIX || ('always' as any),
    localeDetection: true
  })

  const response = handleI18nRouting(request)

  if (response.cookies.get('NEXT_LOCALE' as any)) {
    response.cookies.delete('NEXT_LOCALE' as any)
  }

  return response
}

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)']
}
```

{% endcode %}

***

## message (/src/data/i18n)

message는 로컬에서 제공하거나 원격 데이터 소스에서 로드할 수 있습니다. 가장 간단한 옵션은 locale을 기반으로 한 JSON 파일을 프로젝트에 추가하는 것입니다.

예시 JSON:

```json
{
  "common-page": {
    "loggedIn": "Logged In",
    "loggedOut": "Logged Out",
    "login": "Sign In",
    "logout": "Log Out",
    "Settings": "Settings",
    "title": "Account"
  }
}
```

다국어 Json파일은 i18n폴더에 언어별 폴더에 json 파일을 넣으면 되며, 각 언어와 json 파일을 매핑하기 위해서는 i18n파일에 해당 json 경로를 명시해주면 됩니다.

### /src/i18n.ts

{% code title="/src/i18n.ts" %}

```ts
import { getRequestConfig } from 'next-intl/server'

export default getRequestConfig(async ({ locale }) => {
  // messages json 파일들 경로를 명시
  const combinedMessages = {
    ...(await import(`/src/data/i18n/${locale}/common.json`)),
    ...(await import(`/src/data/i18n/${locale}/display.json`)),
    ...(await import(`/src/data/i18n/${locale}/event.json`)),
    ...(await import(`/src/data/i18n/${locale}/goods.json`)),
    ...(await import(`/src/data/i18n/${locale}/member.json`)),
    ...(await import(`/src/data/i18n/${locale}/order.json`)),
    ...(await import(`/src/data/i18n/${locale}/promotion.json`)),
    ...(await import(`/src/data/i18n/${locale}/search.json`))
  }

  return { messages: combinedMessages }
})
```

{% endcode %}

### /src/data

<div align="left"><figure><img src="/files/HryXbWIfDWh7cxhcT5ZG" alt=""><figcaption></figcaption></figure></div>

message를 사용하기 위해 server와 client단에서 제공해주는 함수가 다른데 공통된 함수로 사용하기 위해 분기처리하여 해당하는 함수를 return 해 줍니다.

{% code title="messageClient (예시)" %}

```ts
import { getTranslations } from 'next-intl/server'
import { useTranslations } from 'next-intl';
import { isNull } from '@/lib/x2bee-core';

export function getMessage(namespace: string): any {
  if (isNull(namespace)) {
    throw new Error('Message Client namespace value is not null.');
  }

  const isServerComponent: () => (boolean) = () => {
    return typeof window === 'undefined' ? true : false;
  };

  // server와 client 분기처리
  if (isServerComponent()) {
    let localeTranslations = {}
    try {
      localeTranslations = getTranslations(namespace);
    } catch(error) {
      localeTranslations = useTranslations(namespace);
    }
    return localeTranslations;
  } else {
    return useTranslations(namespace);
  }
}
```

{% endcode %}

***

## Server에서 사용 방법

{% code title="Server Component (예시)" %}

```tsx
import { getMessage } from '@/lib/common/plugins/messageClient'

const TestPage = async () => {
  const t = await getMessage('account-page')
  console.log(t)

  return (
    <>
      <h1>{t('loggedIn')}</h1>
    </>
  )
}

export default TestPage
```

{% endcode %}

## Client에서 사용 방법

{% code title="Client Component (예시)" %}

```tsx
'use client'
import { getMessage } from '@/lib/common/plugins/messageClient'

const TestPage = () => {
  const t = getMessage('account-page')
  console.log(t)

  return (
    <>
      <h1>{t('loggedIn')}</h1>
    </>
  )
}

export default TestPage
```

{% endcode %}

***

## Navigation

next-intl은 사용자 locale을 자동으로 처리하는 공통 Next.js 네비게이션 API에 대한 솔루션을 제공합니다.

{% code title="/src/navigation.ts" %}

```ts
import { createLocalizedPathnamesNavigation, Pathnames } from 'next-intl/navigation'

export const locales = ['ko', 'en'] as const
export const localePrefix = 'always'

// Default export
export const pathnames = {} satisfies Pathnames<typeof locales>

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createLocalizedPathnamesNavigation({
    locales,
    localePrefix,
    pathnames
  })
```

{% endcode %}

Navigation을 사용하여 locale 변경 및 페이지 이동을 구현할 수 있습니다. 아래 예시 소스는 위에 설정한 navigation에서 route와 Link를 사용하여 locale 변경 및 페이지 이동하는 소스입니다.

{% code title="Locale 변경 예시 (Client Component)" %}

```tsx
'use client'
import { getMessage } from '@/lib/common/plugins/messageClient'
// next-intl navigation 가져오기
import { Link, useRouter, usePathname } from '@/navigation'

const TestPage = () => {
  const t = getMessage('common-page')
  const pathname = usePathname()
  const router = useRouter()

  // router를 사용한 방식
  const handleChange = (event) => {
    router.replace(pathname, { locale: event.target.value })
  }

  return (
    <>
      <h1>{t('loggedIn')}</h1>

      <select onChange={handleChange}>
        <option>ko</option>
        <option>en</option>
      </select>

      {/* Link를 사용한 방식 */}
      <Link href="/goods" locale="en"> Switch to German </Link>
    </>
  )
}

export default TestPage
```

{% endcode %}

***


# 5. Error Handling

이 문서는 Error handling에 대해 설명합니다. 특히 JavaScript 서버 액션에서의 오류 처리와 중첩된 경로에서의 404 오류 처리에 대해 중점적으로 다룹니다.

***

## 서버 액션에 try/catch 추가

먼저 오류를 적절하게 처리할 수 있도록 JavaScript의 try/catch 문을 서버 액션에 추가합니다.

서버 액션을 업데이트하는 데 몇 분 정도 시간을 투자하거나 아래 코드를 복사할 수 있습니다.

{% stepper %}
{% step %}

### Create Invoice (createInvoice)

{% code title="/app/lib/actions.ts — createInvoice" %}

```
```

{% endcode %}
{% endstep %}

{% step %}

### Update Invoice (updateInvoice)

{% code title="/app/lib/actions.ts — updateInvoice" %}

```
```

{% endcode %}
{% endstep %}

{% step %}

### Delete Invoice (deleteInvoice)

{% code title="/app/lib/actions.ts — deleteInvoice" %}

```
```

{% endcode %}
{% endstep %}
{% endstepper %}

서버 액션에서 오류를 강제로 발생시키면 다음과 같이 동작합니다:

{% code title="/app/lib/actions.ts — deleteInvoice (throwing error example)" %}

```
```

{% endcode %}

이제 서버 액션에서 오류를 발생시키면 로컬호스트에서 오류가 표시됩니다. 개발 중에는 이러한 오류를 확인해 잠재적 문제를 조기에 발견할 수 있으며, 사용자에게 적절한 오류 메시지를 제공해 애플리케이션이 계속 실행되도록 도울 수 있습니다.

{% hint style="info" %}
서버 액션에서 오류를 반환할 때는 사용자에게 노출되는 메시지에 민감 정보가 포함되지 않도록 주의하세요. 또한 로깅/오류 리포팅 도구에 에러를 전송해 추적할 수 있도록 하면 운영 환경에서 유용합니다.
{% endhint %}

## Nested Routes

에러는 가장 가까운 부모 에러 바운더리로 전파됩니다. 그러므로 error.tsx 파일이 있는 상위 세그먼트가 중첩된 하위 세그먼트의 에러를 처리할 수 있습니다. 단, 에러 바운더리는 동일 세그먼트 내의 layout.js에서 발생한 에러를 처리하지 않습니다(에러 바운더리가 해당 레이아웃 컴포넌트 내에 중첩되어 있기 때문).

### error.tsx로 모든 오류 처리

error.tsx 파일은 경로 세그먼트에 대한 UI 경계를 정의하는 데 사용됩니다. 예상치 못한 오류에 대한 포괄적인 역할을 하며 사용자에게 대체 UI를 표시할 수 있습니다.

{% code title="error.tsx" %}

```
```

{% endcode %}

예: 위와 같은 error.tsx 파일을 만들고 에러를 발생시키면 사용자에게 대체 UI가 표시됩니다.

<figure><img src="/files/IzlLyUtjEXIjS1tmEnW5" alt=""><figcaption></figcaption></figure>

### notFound 함수로 404 오류 처리

존재하지 않는 리소스를 가져오려고 할 때는 notFound 함수를 사용하는 것이 유용합니다. error.tsx는 모든 오류를 잡는 데 유용하지만, 리소스 부재(404)를 더 구체적으로 처리하려면 notFound를 사용하세요.

{% code title="/dashboard/invoices/\[id]/edit/page.tsx" %}

```
```

{% endcode %}

위 예제는 invoice 값이 없으면 notFound 페이지로 이동합니다.

<figure><img src="/files/I7CCawBelPASDmBYxi8C" alt=""><figcaption></figcaption></figure>

아래는 해당 경로에 대한 not-found 컴포넌트 예시입니다.

{% code title="/dashboard/invoices/\[id]/edit/not-found.tsx" %}

```
```

{% endcode %}

notFound로 404 처리를 하면 다음과 같은 UI가 표시됩니다.

<figure><img src="/files/NeEuiByhhNRzDtLaCFtJ" alt=""><figcaption></figcaption></figure>

notFound는 error.tsx보다 우선하므로, 더 구체적인 404 처리가 필요할 때 활용하세요.

<div align="left"><figure><img src="/files/tWcKXyZmDAcMso55DQSO" alt=""><figcaption></figcaption></figure></div>

전체적으로 경로가 없을 시 not found 처리를 하고 싶다면 \[...not\_found]와 같은 catch-all 세그먼트에 notFound 호출을 적용하여, 요청한 페이지가 없으면 notFound 처리하도록 구현할 수 있습니다.


# 6. Parallel Routes, Intercepting Routes

이 문서는 Parallel Routes와 Intercepting Routes에 대한 설명과 사용하는 방법에 대해 설명합니다.

***

## Parallel Routes

Parallel Routes는 동시에 또는 조건부로 동일한 레이아웃 내에서 하나 이상의 페이지를 렌더링할 수 있게 해줍니다. 이는 앱의 매우 동적인 섹션인 대시보드나 소셜 사이트의 피드와 같은 경우에 유용합니다.

예를 들어, 대시보드를 고려해보면 병렬 루트를 사용하여 팀 및 분석 페이지를 동시에 렌더링할 수 있습니다.

<figure><img src="/files/vOKSQsQIcSKTCJ123kTM" alt=""><figcaption></figcaption></figure>

Parallel Routes는 명명된 슬롯을 사용하여 생성됩니다. 슬롯은 @폴더 관례로 정의됩니다. 아래 예시는 @team 및 @user 두 개의 슬롯을 정의합니다.

<div align="left"><figure><img src="/files/dQ0cIpqpg1KlZzGJItGO" alt=""><figcaption></figcaption></figure></div>

슬롯은 공유 부모 레이아웃에 속성(props)으로 전달됩니다. 위의 예에서는 app/layout.js의 컴포넌트가 이제 @team 및 @user 슬롯 속성을 받아들이고, 이를 자식 속성(children prop)과 함께 병렬로 렌더링할 수 있습니다.

```tsx
export default function Layout({
  children,
  user,
  team,
}: {
  children: React.ReactNode
  user: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <section>
      {children}
      {user}
      {team}
    </section>
  )
}
```

슬롯은 루트 세그먼트가 아니며 URL 구조에 영향을 미치지 않습니다.

### default.js

초기 로드 또는 전체 페이지 다시 로드 중에 일치하지 않는 슬롯에 대한 대체 항목으로 렌더링할 파일을 정의할 수 있습니다.

<div align="left"><figure><img src="/files/roxmm0lSf5msELu5kWq4" alt=""><figcaption></figcaption></figure></div>

위와 같은 경로가 있다고 가정했을 경우 @team 폴더에는 settings라는 폴더가 있지만 @user의 경우는 폴더가 없습니다. 그래서 '/dashboard/settings/'에 접근 시 default.js가 없을 시 404에러가 발생하게 되는데 default.js 파일을 만들어줌으로써 이를 방지할 수 있습니다.

### 조건부 경로

Parallel Routes는 특정 조건에 따라 슬롯을 조건부로 렌더링할 수 있도록 허용합니다. 예를 들어 언어가 한국일때만 user page를 보여주고 다른 경우는 team page를 보고 싶을 시 다음과 같이 작성할 수 있습니다.

```tsx
export default function Layout({
  children,
  user,
  team,
  params,
}: {
  children: React.ReactNode
  user: React.ReactNode
  team: React.ReactNode
  params: { lang: string }
}) {
  // default는 team page
  let page = team

  // lang이 ko면 user page
  if (params.lang == 'ko') {
    page = user
  }

  return (
    <section>
      {children}
      {page}
    </section>
  )
}
```

<figure><img src="/files/dEwtFMMWzl4lf4jtSXjE" alt=""><figcaption></figcaption></figure>

### Streaming

Parallel Routes는 독립적으로 스트리밍될 수 있어 각 루트에 대해 독립적인 오류 및 로딩 상태를 정의할 수 있습니다.

<figure><img src="/files/ZJaTSzyXo8JRXHBoFgzN" alt=""><figcaption></figcaption></figure>

## 구현 예시 (Parallel Routes)

{% stepper %}
{% step %}

### 클라이언트 페이지 예시 (공통)

파일 예시: @user 및 @team에 동일한 페이지 컴포넌트 생성

```tsx
'use client'
import { useState } from "react";

export default function Page() {
  const [backgroundColor, setBackgroundColor] = useState('blue');

  const handleColorChange = () => {
    // 랜덤한 배경색을 생성하기 위한 함수
    const getRandomColor = () => {
      const letters = '0123456789ABCDEF';
      let color = '#';
      for (let i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
      }
      return color;
    };

    // 새로운 랜덤한 배경색으로 설정
    const newColor = getRandomColor();
    setBackgroundColor(newColor);
  };

  return (
    <>
      <div className="w-1/2">
        <button className="w-full" onClick={handleColorChange}>change button</button>
        <div className="w-full h-full">
          <div
            style={{
              backgroundColor: backgroundColor,
              padding: '20px',
              textAlign: 'center',
              cursor: 'pointer',
            }}
            className="w-full h-96"
          >
            team page
          </div>
        </div>
      </div>
    </>
  );
}
```

(위 파일을 @user와 @team에 각각 배치)
{% endstep %}

{% step %}

### 레이아웃에 슬롯 추가

파일 예시: app/layout.tsx (또는 app/\[lang]/layout.tsx 등)

```tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function Layout({
  children,
  user,
  team,
  params,
}: {
  children: React.ReactNode
  user: React.ReactNode
  team: React.ReactNode
  params: { lang: string }
}) {
  return (
    <section>
      {children}
      <div className="flex">
        {team}
        {user}
      </div>
    </section>
  )
}
```

그 다음 실행하면 상단의 버튼 클릭 시 각각의 route event가 발생하여 색상이 변하는 것을 확인할 수 있습니다.

<figure><img src="/files/pshLNwcKCulOdgCXcymy" alt="" width="563"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

***

## Intercepting Routes

Intercepting Routes는 현재 레이아웃 내에서 애플리케이션의 다른 부분에서 루트를 로드할 수 있게 해줍니다. 이 라우팅 패러다임은 사용자가 다른 컨텍스트로 전환하지 않고 루트의 내용을 표시하고자 할 때 유용할 수 있습니다.

예를 들어, 피드에서 사진을 클릭할 때 해당 사진을 모달에서 표시할 수 있습니다. 이 경우 Next.js는 /photo/123 루트를 가로채고 URL을 마스킹하여 /feed 위에 오버레이합니다.

<figure><img src="/files/dBT3PWlKmxcEHQZwRzIq" alt=""><figcaption></figcaption></figure>

그러나 공유 가능한 URL을 클릭하거나 페이지를 새로 고침하여 사진에 접근할 때에는 모달 대신 전체 사진 페이지가 렌더링되어야 합니다. 이때는 라우트 가로채기가 발생하지 않아야 합니다.

<figure><img src="/files/Pqw0uwgvbEANRP2MmYxg" alt=""><figcaption></figcaption></figure>

Intercepting Routes는 (..) 관례를 사용하여 정의할 수 있으며, 이는 상대 경로 관례 ../과 유사하지만 세그먼트를 위한 것입니다.

다음과 같이 사용할 수 있습니다:

* (.) : 동일한 레벨의 세그먼트와 일치
* (..) : 하나 위 레벨의 세그먼트와 일치
* (..)(..) : 두 레벨 위의 세그먼트와 일치
* (...) : 루트 app 디렉토리에서 세그먼트와 일치

예를 들어, 피드 세그먼트 내에서 (..)photo 디렉토리를 생성하여 피드 내부에서 사진 세그먼트를 가로챌 수 있습니다.

<figure><img src="/files/tA9RzThnddPJnT20y9h2" alt=""><figcaption></figcaption></figure>

### Modals

Intercepting Routes를 Parallel Routes와 함께 사용하여 모달을 만들 수 있습니다.

이 패턴을 사용하여 모달을 만들면 모달과 관련된 몇 가지 일반적인 문제를 극복할 수 있으며, 다음과 같은 기능이 가능해집니다.

* 모달 콘텐츠를 URL을 통해 공유 가능하게 함
* 페이지를 새로 고침할 때 모달을 닫는 대신 컨텍스트를 보존
* 이전 라우트로 이동하는 대신 모달을 닫음
* 앞으로 이동하는 경우 모달을 다시 열 수 있음

<figure><img src="/files/4yTfmftKHiDFGtLDTnjU" alt=""><figcaption></figcaption></figure>

## 구현 예시 (Intercepting Routes + Modal)

<div align="left"><figure><img src="/files/FLYfFDLk863ia8Z5YILW" alt=""><figcaption></figcaption></figure></div>

다음은 모달 인터셉트 패턴의 예시 코드입니다.

파일: /@modal/(.)photos/\[id]/modal.tsx

```tsx
'use client'
import React, { ElementRef, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import { createPortal } from "react-dom";

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  const dialogRef = useRef<ElementRef<'dialog'>>(null);

  useEffect(() => {
    if (!dialogRef.current?.open) {
      dialogRef.current?.showModal();
    }
  }, []);

  function onDismiss() {
    router.back();
  }

  return createPortal(
    <div className="modal-backdrop">
      <dialog ref={dialogRef} className="modal" onClose={onDismiss}>
        {children}
        <button onClick={onDismiss} className="close-button" />
      </dialog>
    </div>,
    document.getElementById('modal-root')!
  );
}
```

파일: @modal/(.)photos/\[id]/page.tsx

```tsx
import { Modal } from "@/app/[lang]/@modal/(.)photos/[id]/modal";

export default function PhotoModal({
  params: { id: photoId },
}: {
  params: { id: string };
}) {
  return <Modal>{photoId}</Modal>;
}
```

파일: photos/\[id]/page.tsx (전체 페이지 렌더링용)

```tsx
import { Modal } from "@/app/[lang]/@modal/(.)photos/[id]/modal";

export default function PhotoPage({
  params: { id: photoId },
}: {
  params: { id: string };
}) {
  return <Modal>{photoId}</Modal>;
}
```

레이아웃에 Parallel Routes의 modal 슬롯을 추가:

```tsx
<section>
  {children}
  {modal}
  <div id="modal-root" />
</section>
```

<figure><img src="/files/fIVoE3Cd0dC5KBEZe62e" alt="" width="356"><figcaption></figcaption></figure>

이 구성으로, 피드에서 버튼을 클릭하면 /ko/photos/id로 요청이 가지만, Intercepting Routes가 이를 @modal로 가로채 동일한 화면에서 모달을 띄웁니다.

<figure><img src="/files/RYIxRwtJ8iwQqIJVvTly" alt="" width="178"><figcaption></figcaption></figure>

직접 /ko/photos/id로 접근하거나 공유 가능한 URL을 통해 접근하면 모달이 아닌 전체 사진 페이지가 렌더링됩니다(가로채기가 발생하지 않음).

<figure><img src="/files/fhgHfEEXTTr2lWSLsXV3" alt=""><figcaption></figcaption></figure>

***

(문서 내 예시 이미지 및 코드 스니펫은 설명을 위해 포함되어 있습니다.)


# 7. 기타 (loading, Suspense, zod 등)

이 문서는 Next.js 프로젝트에서 loading, error boundary, zod, dynamic routes, searchParams를 사용하는 예시 코드를 제공합니다.

***

## 1. Dynamic Routes

Dynamic Routes는 관행적으로 \[slug]라는 이름으로 많은 상품명에 따라 동일한 레이아웃을 가져갈 때 사용됩니다. 반드시 slug라는 이름을 사용해야 하는 것은 아니며, 가독성을 위해 아래는 `categoryname`이라는 이름으로 예시를 작성하였습니다.

다음 폴더 구조에서의 예시

```
└── category
    └── [categoryname]
        └── page.tsx
```

page.tsx

{% code title="app/category/\[categoryname]/page.tsx" %}

```tsx
import React from "react";

const Page = ({ params }: { params: { categoryname: string } }) => {
  return (
    <div>
      <h1>{params.categoryname}</h1>
    </div>
  );
};

export default Page;
```

{% endcode %}

브라우저에 <http://localhost:3000/category/dress> 를 입력하면 `dress`가 렌더링됩니다.

React에서 react-router-dom을 사용하면 Nested Routing 설정으로 코드가 길어질 수 있지만, Next.js에서는 위와 같이 `params`를 직접 받을 수 있어 간결합니다.

***

## 2. searchParams

`searchParams`는 상품 검색 시 URI로 검색 정보를 넘길 때 사용됩니다.

서버 컴포넌트(페이지) 예시:

{% code title="app/somepage/page.tsx" %}

```tsx
const Page = ({
  searchParams,
}: {
  searchParams: { id: string | undefined };
}) => {
  return (
    <div>
      <h1>{searchParams.id}</h1>
    </div>
  );
};

export default Page;
```

{% endcode %}

클라이언트 컴포넌트에서 동일한 기능 구현:

{% code title="app/somepage/client-page.tsx" %}

```tsx
"use client";
import { useSearchParams } from "next/navigation";

const Page = () => {
  const searchParams = useSearchParams();
  const id = searchParams.get("id");

  return (
    <div>
      <h1>{id}</h1>
    </div>
  );
};

export default Page;
```

{% endcode %}

***

## 3. zod, loading, error boundary, dynamic routes 사용 예시

폴더 구조 예시:

```
├── events
│   ├── [id]
│   │   ├── loading.tsx
│   │   └── page.tsx
│   └── error.tsx
```

zod 설치:

```
pnpm add zod
```

아래 예시는 `http://localhost:8077/events/seoul?sp1=test&pageno=2` 접속 시

* params = { id: 'seoul' }
* searchParams = { sp1: 'test', pageno: '2' }

page.tsx (서버 컴포넌트)

{% code title="app/events/\[id]/page.tsx" %}

```tsx
import { Suspense } from 'react';
import Loading from './loading';
import { z } from 'zod';

interface Props {
  params: { id: string };
  searchParams: { [key: string]: string | string[] | undefined };
}

// searchParam 값은 string이므로 숫자로 바꿔주고, 정수이면서 양수인지를 체크
const pageNumberSchema = z.coerce.number().int().positive();

const Page = ({ params, searchParams }: Props) => {
  console.log('search', searchParams);

  const parsedPageNo = pageNumberSchema.safeParse(searchParams.pageno);
  console.log('parsedPageNo', parsedPageNo);
  // pageno값이 '2'이면 결과 : parsedPageNo { success: true, data: 2 }
  // pageno값이 '0'이면 결과 : parsedPageNo { success: false, error: [Getter] }

  if (!parsedPageNo.success) {
    // 즉, 양수가 아니면 에러를 던진다.
    throw new Error('Invalid page number');
  }

  // error.tsx는 같은 폴더에 있어도 되지만 없다면 상위 폴더의 error.tsx가 실행된다.
  return (
    <main className="py-24 text-center">
      <Suspense key={parsedPageNo.data} fallback={<Loading />}>
        <p>page number is {parsedPageNo.data}</p>
      </Suspense>
    </main>
  );
};

export default Page;
```

{% endcode %}

loading.tsx에는 spinner 애니메이션이나 아이콘을 등록할 수 있고, UI 라이브러리를 이용하여 `<Skeleton />`을 등록할 수도 있습니다.

loading.tsx 예시 (간단)

{% code title="app/events/\[id]/loading.tsx" %}

```tsx
export default function Loading() {
  return (
    <div className="py-24 text-center">
      <p>Loading...</p>
    </div>
  );
}
```

{% endcode %}

error.tsx는 반드시 Client Component에서만 동작합니다.

error.tsx (클라이언트 전용)

{% code title="app/events/error.tsx" %}

```tsx
'use client'; // Error components must be Client Components
import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);

  return (
    <main className="py-24 text-center">
      <p>{error.message}</p>
      <button
        className="mt-4 border bg-blue-500 px-4 py-2 text-white"
        onClick={
          // Attempt to recover by trying to re-render the segment
          reset
        }
      >
        Try again
      </button>
    </main>
  );
}
```

{% endcode %}

만약 `error.tsx`가 없다면, 상위 레벨의 에러 바운더리가 없을 경우 앱 전체가 crash되는 현상이 발생할 수 있습니다. 그러므로 각 세그먼트(폴더)에 에러 컴포넌트를 배치하여 앱 전체의 크래시를 방지하고, 사용자에게 재시도 등 복구 수단을 제공하는 것이 좋습니다.

<figure><img src="/files/d0agJDPZ8RnTSBw3bYqc" alt=""><figcaption></figcaption></figure>

<div align="left"><figure><img src="/files/iZ5aETIbPGv5Ag4BXZM9" alt=""><figcaption></figcaption></figure></div>

위의 경우는 searchParams로 유효성 검사를 하였지만 react-query 혹은 fetch를 이용하여서도 동일한 방식으로 적용될 수 있습니다.


# 03. 퍼블 가이드

최근의 CSS 프레임워크 중 가장 선호도가 높은 프레임워크인 Tailwind 를 설명하는 장입니다.

최근까지 UI Framework 상에서는 CSS in JS 프레임워크가 가장 선호 되었지만, Tailwind 로 인하여 추세가 변하고 있습니다.

Tailwind 로 CSS 로 전개하면, React, Vue 등의 UI Framework 별로 전개되는 방식이 다른 CSS in JS 전략을 한 가지로 통일 할 수 있습니다.

이를 통해, 디자이너, 퍼블리셔 와 개발자 간의 협업을 더 공고히 할 수 있고, 같은 언어로 소통 할 수 있습니다.


# 1. Local font

Created by 정우문, last modified by 정지민 on 2024-02-07

이 문서는 로컬 폰트 파일을 사용하여 Nextjs 앱에서 사용자 정의 폰트를 설정하는 방법입니다.

***

{% stepper %}
{% step %}

### Local font files

* src/lib/common/ui/fonts 폴더를 생성하고 Pretendard 폰트 파일을 복사합니다. ( public/assets/fonts 폴더를 생성하여도 무관 )

<figure><img src="/files/U9u9Pe8ZXXVPr10bD0Gs" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### src/lib/common/ui/ 폴더에 font.ts 파일을 생성합니다.

다음 코드를 입력합니다.

{% code title="src/lib/common/ui/font.ts" %}

```typescript
import localFont from 'next/font/local';

export const fontDefault = localFont({
  src: [
{ path: './fonts/Pretendard-Bold.woff2', weight: '700', style: 'bold' },
{ path: './fonts/Pretendard-Medium.woff2', weight: '500', style: 'medium' },
    { path: './fonts/Pretendard-Regular.woff2', weight: '400', style: 'normal' }
  ],
  display: 'swap',
variable: '--font-default'
});
```

{% endcode %}
{% endstep %}

{% step %}

### layout.tsx

`src/app/layout.tsx`에 import 후 `html`에 variable로 폰트를 지정합니다.

예:

{% code title="src/app/\[locale]/layout.tsx" %}

```tsx
import { fontDefault } from '@/lib/common/ui/font';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
<html lang="ko" className={`${fontDefault.variable}`}>
      <body>{children}</body>
    </html>
  );
}
```

{% endcode %}
{% endstep %}

{% step %}

### config (tailwind.config.js)

Tailwind 설정에서 커스텀 폰트를 추가합니다.

{% code title="tailwind.config.js" %}

```javascript
module.exports = {
  // ...
  theme: {
    extend: {
      fontFamily: {
default: ['var(--font-default)']
      }
    }
  }
};
```

{% endcode %}
{% endstep %}

{% step %}

### globals.css

글로벌 스타일에서 기본 폰트로 적용합니다.

{% code title="globals.css" %}

```css
@layer base {
  html[lang='ko'] {
@apply font-default;
  }
}
```

{% endcode %}
{% endstep %}
{% endstepper %}

이렇게 설정해주면 완료됩니다.


# 2. Checkbox

이 문서는 로컬 아이콘을 사용하여 체크박스에 아이콘을 적용하는 방법에 대한 가이드입니다.

{% stepper %}
{% step %}

### 1) 로컬 아이콘 준비

checkbox에 사용할 아이콘을 `src/assets/icons/` 폴더에 저장합니다. (폴더 이름은 자유)

<div align="left"><figure><img src="/files/TiBFiSBGH1iF8cIgBnDw" alt="" width="252"><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

### 2) tailwind.config.js 설정

`tailwind.config.js`에 다음과 같이 backgroundImage를 추가합니다. (경로와 파일명은 프로젝트에 맞게 수정)

{% code title="tailwind.config.js" %}

```
```

{% endcode %}

```js
theme: {
  extend: {
    backgroundImage: {
      'icon-checkbox': "url('~/src/assets/icons/ico_checkbox.svg')",
      'icon-checkbox-on': "url('~/src/assets/icons/ico_checkbox_on.svg')",
      'icon-checkbox-dis': "url('~/src/assets/icons/ico_checkbox_dis.svg')",
    },
  }
}
```

이제 아이콘 이미지를 커스텀 유틸리티 클래스(background-image)로 사용할 수 있습니다.
{% endstep %}

{% step %}

### 3) CSS 파일 분리 및 import

`src/app/globals.css`에 직접 추가해도 되지만 리팩토링을 위해 `src/assets/css/checkbox.css` 파일을 생성하고, `globals.css`에서 import 합니다:

<div align="left"><figure><img src="/files/2CxLM4zWhRVkFn2CLiwR" alt="" width="278"><figcaption></figcaption></figure></div>

```
@import url('../assets/css/checkbox.css');
```

```
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  input {
    @apply appearance-none;
  }
}

@layer components {
  .checkbox {
    @apply mx-2 inline-block h-6 w-6 shrink-0 cursor-pointer bg-icon-checkbox bg-contain bg-center bg-no-repeat align-middle checked:bg-icon-checkbox-on disabled:bg-icon-checkbox-dis;
  }
}
```

설명:

* 기본 Tailwind reset에서 input 태그를 완전히 초기화하지 않으므로 `@apply appearance-none;`로 다시 리셋합니다.
* `.checkbox` 클래스에 앞서 설정한 background-image 유틸리티를 사용합니다.&#x20;

### 5) 컴포넌트 내 사용 예시

React(또는 Next.js) 컴포넌트 예시:

```jsx
const CheckBox = () => {
  return (
    <div className="inline-flex gap-4 rounded-lg border border-gray-400 bg-white p-8">
      <div>
        <input className="checkbox" id="chk1" type="checkbox" />
        <label htmlFor="chk1">label</label>
      </div>
      <div>
        <input className="checkbox" id="chk2" type="checkbox" disabled={true} />
        <label htmlFor="chk2">disabled</label>
      </div>
    </div>
  );
};

export default CheckBox;
```

{% endstep %}

{% step %}

### 6) 결과

#### local icons <a href="#local-icons.1" id="local-icons.1"></a>

checkbox에 사용할 icon을 src/assets/icons/ 폴더에 저장합니다. (폴더 이름은 자유)

<img src="https://blog.kakaocdn.net/dn/NaapR/btsBY2VsgPa/hHX3xKeEVcsTkbzlPKcOfK/img.png" alt="" width="375">

#### config <a href="#config.1" id="config.1"></a>

tailwind.config.js에 다음 코드를 추가합니다.

```
 theme: {
    extend: {
      backgroundImage: {
        'icon-checkbox': "url('~/src/assets/icons/ico_checkbox.svg')",
        'icon-checkbox-on': "url('~/src/assets/icons/ico_checkbox_on.svg')",
        'icon-checkbox-dis': "url('~/src/assets/icons/ico_checkbox_dis.svg')",
      },
```

이제 아이콘 이미지를 custom utility class로 사용할 수 있습니다.

#### css <a href="#css.1" id="css.1"></a>

src/app/globals.css 에 코드를 추가해도 되지만,

refactoring을 위해 src/assets/css/checkbox.css 파일 생성하고,

<div align="left"><img src="https://blog.kakaocdn.net/dn/cpPumY/btsB1JHEwvM/MU0aneh0dLFj6i2BjWpWqk/img.png" alt="" width="375"></div>

src/app/globals.css에서 import 합니다.

`@import url('../assets/css/checkbox.css');`

&#x20;

다시 checkbox.css로 돌아와 위에서 정의한 utility class를 다음과 같이 작성합니다.

그리고 tailwind css 기본 reset css에서 input tag를 완전히 reset 시켜주지 않으므로 여기서 다시 reset 합니다.

```
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  input {
    @apply appearance-none;
  }
}

@layer components {
  .checkbox {
    @apply mx-2 inline-block h-6 w-6 shrink-0 cursor-pointer bg-icon-checkbox bg-contain bg-center bg-no-repeat align-middle checked:bg-icon-checkbox-on disabled:bg-icon-checkbox-dis;
  }
}
```

#### component내에서 사용 예시 <a href="#component-.1" id="component-.1"></a>

```
const CheckBox = () => {
  return (
    <div className="inline-flex gap-4 rounded-lg border border-gray-400 bg-white p-8">
      <div>
        <input className="checkbox" id="chk1" type="checkbox" />
        <label htmlFor="chk1">label</label>
      </div>
      <div>
        <input className="checkbox" id="chk2" type="checkbox" disabled={true} />
        <label htmlFor="chk2">disabled</label>
      </div>
    </div>
  );
};

export default CheckBox;
```

#### 결과 <a href="#id-1" id="id-1"></a>

<img src="https://tech.x2bee.com/download/attachments/196706477/image-20231217-193338.png?version=1&#x26;modificationDate=1702841622953&#x26;cacheVersion=1&#x26;api=v2" alt="" width="375">
{% endstep %}
{% endstepper %}


# 3. Button variants

이 문서는 다양한 버튼 스타일 및 변형을 쉽게 구현하기 위한 방법을 안내하는 가이드입니다.

***

### Button UI

Button은 variant가 많고, Tailwind CSS는 nested selector를 지양합니다.\
그러므로 React 컴포넌트로 구현하는 방식을 권장합니다.

### 설치

다음 패키지 3가지를 추가합니다. 가이드 2-3을 참조하여 `src/lib/utils.ts`를 설정하세요.

{% code title="설치 (pnpm)" %}

```bash
pnpm add tailwind-merge clsx class-variance-authority
```

{% endcode %}

### Button library 예시

아래는 예시로 구현한 Button 컴포넌트와 사용 예시입니다.

src/components/ui/button.tsx

{% code title="src/components/ui/button.tsx" %}

```typescript
import { VariantProps, cva } from 'class-variance-authority';
import { ComponentProps } from 'react';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none data-[state=open]:bg-slate-100',
  {
    variants: {
      bgcolor: {
        default: 'bg-slate-900 text-slate-50 hover:bg-slate-700',
        pink: 'bg-pink-900 text-pink-50 hover:bg-pink-500',
      },
      outline: {
        default: 'border border-transparent',
        black: 'border-4 border-black',
      },
      size: {
        default: 'h-10 py-2 px-4 text-sm',
        sm: 'h-9 px-2 rounded-md text-xs',
        lg: 'h-11 px-8 rounded-md text-base',
      },
    },
    defaultVariants: {
      bgcolor: 'default',
      outline: 'default',
      size: 'default',
    },
  }
);

interface ButtonProps extends ComponentProps<'button'>, VariantProps<typeof buttonVariants> {}

const Button = ({ className, bgcolor, outline, size, ...props }: ButtonProps) => {
  return (
    <button
      className={cn(
        buttonVariants({
          bgcolor,
          outline,
          size,
          className,
        })
      )}
      {...props}
    />
  );
};

export default Button;
```

{% endcode %}

Component에서 Button 사용 예시 (page.tsx)

{% code title="사용 예시 — page.tsx" %}

```tsx
import Button from '@/components/ui/button';

const ButtonPage = () => {
  return (
    <>
      <Button>My Button 1</Button>
      <Button bgcolor="pink">My Button 2</Button>
      <Button bgcolor="pink" outline="black"> My Button 3 </Button>
      <Button bgcolor="pink" outline="black" size="lg"> My Button 4 </Button>
    </>
  );
};

export default ButtonPage;
```

{% endcode %}

추가 참고: 설치 후 utils 설정은 가이드 2-3을 참조하세요.

링크: [Atlassian](http://www.atlassian.com/)


# 04. Data Fetching

이번 장에서는 데이터 통신 방법에 대해서 설명합니다.

이전 세대 프레임워크에서는 Rest Api 통신 시에 XMLHTTPRequest 기반의 axios 를 위주로 다루었지만, 최근에는 새로운 Native Javascript 통신 방법인 Fetch API 로 대체되고 있습니다.

Fetch API는 기본적으로 Promise 를 사용하기 때문에 비동기 요청을 더 쉽게 만들고 응답을 더 잘 처리할 수 있게 해줍니다.


# 1. X2BEE-core 팩키지 설치

이 문서는 x2bee-core 패키지를 설치하고 사용하는 방법에 대한 안내입니다.

***

### Data fetching 이란?

Data fetching이란 API 등을 이용하여 데이터를 가져오는 과정을 의미하며, 대표적으로 자바스크립트에서 네이티브로 제공해주는 XMLHttpRequest, Fetch API 그리고 jQuery의 AJAX, Axios 통신 라이브러리를 이용하여 처리합니다.

Next.js 13 이전 버전에서는 `getStaticProps`이나 `getServerSideProps`라는 메소드 또는 컴포넌트 로직 내에서 사용자 행위로 일어나는 이벤트 동작에서 data fetching을 사용하였습니다.

하지만 Next 13부터는 Server Component 및 Client Component라는 구분이 생겨났고, 이로 인하여 data fetching에도 변화가 있었습니다. 기존에는 Axios와 같은 모듈을 이용하였다면, 현재 새로운 Next 13 및 14 버전에서는 기본적으로 제공하는 fetch() API를 사용하는 것으로 변경되었습니다. (참조: <https://developer.mozilla.org/ko/docs/Web/API/Fetch\\_API/Using\\_Fetch>)

여기에서는 기본 fetch API를 확장한 `restApiUtil` 클래스를 사용합니다. 공통으로 사용되는 헤더, Token 등의 값 설정이 추가되어 해당 사이트에 맞춰 사용하기 위해 만들어진 `serverRestApi`, `clientRestApi` 클래스 등 총 3가지 util 클래스를 제공합니다.

{% hint style="info" %}
Next.js 13/14 환경에서는 서버/클라이언트 컴포넌트 구분에 따라 fetch 사용 방식이 달라질 수 있으니, 환경에 맞는 `serverRestApi` 또는 `clientRestApi`를 사용하세요.
{% endhint %}

### x2bee-core 설치

다음 절차로 프로젝트에 `x2bee-core` 패키지를 추가하고 설치합니다.

{% stepper %}
{% step %}

### 프로젝트의 package.json에 의존성 추가

`package.json`의 dependencies 섹션에 다음 항목을 추가합니다:

{% code title="package.json (예시)" %}

```json
"dependencies": {
"x2bee-core": "file:src/lib/x2bee-core"
}
```

{% endcode %}
{% endstep %}

{% step %}

### 패키지 설치

의존성 추가 후, 일반적인 패키지 설치 명령어(`npm install` 또는 `yarn install` 등)를 실행하면 `node_modules`에 `x2bee-core`가 추가됩니다.
{% endstep %}
{% endstepper %}

정상적으로 패키지가 설치 완료되면 `node_modules`에 `x2bee-core` 폴더가 생성됩니다.

<figure><img src="/files/csXfDuuD3ZJmt1myum94" alt=""><figcaption></figcaption></figure>


# 2. RestApiUitl 기본 사용

이 문서는 x2bee-core 패키지의 RestApiUtil을 사용하여 데이터를 가져오는 방법에 대한 안내입니다.

x2bee-core에는 RestApiUtil이 포함되어 있습니다.

해당 restApiUtil은 기본 fetch API를 확장하여 다음과 같은 부족한 부분을 보완한 util class 입니다.

{% stepper %}
{% step %}

### 기능: Request / Response 인터셉터

axios에서 제공하는 request interceptor, response interceptor 기능을 지원합니다.
{% endstep %}

{% step %}

### 기능: 자동 직렬화/역직렬화

body 또는 response 데이터를 반환할 경우 text, blob, json 등을 일일이 serialize/deserialize 해야 하는 번거로움을 해소합니다.
{% endstep %}

{% step %}

### 기능: Base URL 설정

base url 설정을 지원합니다.
{% endstep %}

{% step %}

### 기능: Query params 지원

body 외에 params(query) 데이터를 설정할 수 있습니다.
{% endstep %}

{% step %}

### 기능: 공통 응답 포맷

결과 데이터를 공통 ResponseDTO 객체에 담아서 일관되게 반환합니다.
{% endstep %}
{% endstepper %}

***

## 1. Server Component 예시

```javascript
import { COMMON } from '@/constants/x2beeConstants';
import { restApiUtil } from 'x2bee-core';

const Home = async () => {
  const response = await restApiUtil
    .get(COMMON.API_URL + '/api/display/v1/shop/1?dispMediaCd=20');

  console.log(response);

  return (
    <div>
      <h1>Home</h1>
    </div>
  );
};

export default Home;
```

<figure><img src="/files/kDymn3Kz2DPXNiOBewNd" alt=""><figcaption></figcaption></figure>

기본은 ResponseEntity 객체로 반환됩니다.

예: payload만 사용하려는 경우

```javascript
import { COMMON } from '@/constants/x2beeConstants';
import { restApiUtil } from 'x2bee-core';

const Home = async () => {
  const data = await restApiUtil
    .get(COMMON.API_URL + '/api/display/v1/shop/1?dispMediaCd=20')
    .then(response => {
      return response.payload;
    })
    .catch(errorResponse => {
      return errorResponse.payload;
    });

  console.log(data);

  return (
    <div>
      <h1>Home</h1>
    </div>
  );
};

export default Home;
```

<figure><img src="/files/JJ5MNR9DTNMW75rUGycQ" alt=""><figcaption></figcaption></figure>

then 및 catch 절에서 payload 데이터만 반환하는 예시입니다.

***

## 2. Client Component 예시

```javascript
'use client';
import { COMMON } from '@/constants/x2beeConstants';
import { restApiUtil } from 'x2bee-core';

const Home = async () => {
  async function searchData() {
    const data = await restApiUtil
      .get(COMMON.API_URL + '/api/display/v1/shop/1?dispMediaCd=20')
      .then(response => {
        return response.payload;
      })
      .catch(errorResponse => {
        return errorResponse.payload;
      });

    console.log('버튼 클릭');
    console.log(data);
  }

  return (
    <div>
      <h1>Home</h1>
      <button onClick={searchData}>데이터 조회</button>
    </div>
  );
};

export default Home;
```

<figure><img src="/files/jM6u3IC7yRnD4UoZrTfb" alt=""><figcaption></figcaption></figure>

버튼 클릭 시 브라우저 콘솔에서 response 데이터를 확인할 수 있습니다.

***

## 3. restApiUtil 제공 함수 종류

| 함수                     | 설명                                             |
| ---------------------- | ---------------------------------------------- |
| restApiUtil.get()      | GET 메소드는 주로 데이터를 읽거나(Read) 검색(Retrieve)할 때에 사용 |
| restApiUtil.post()     | POST 메소드는 주로 새로운 리소스를 생성(create)할 때 사용         |
| restApiUtil.put()      | PUT는 리소스를 생성 / 업데이트하기 위해 사용                    |
| restApiUtil.delete()   | DELETE 메서드는 지정된 리소스를 삭제 하기 위해 사용               |
| restApiUtil.formPost() | 파일 업로드용 POST 메소드                               |
| restApiUtil.formPut()  | 파일 업로드용 PUT 메소드                                |

***

## 4. restApiUtil Parameter (파라미터)

| 이름                   | 설명                                                |
| -------------------- | ------------------------------------------------- |
| url                  | 요청 url                                            |
| options.baseUrl      | 요청 기본 url                                         |
| options.cache        | cache 사용여부, 기본값은 no-store이며 사용 시에는 force-cache 설정 |
| options.headers      | 요청 header                                         |
| options.params       | 요청 parameter(query). 설정 시 해당값을 url 뒤에 붙여서 완성함     |
| options.body         | 요청 data                                           |
| options.interceptors | request, response function 제공                     |

예시:

```javascript
const response = await restApiUtil.get('/samples/nuxt1', {
  baseUrl: 'http://localhost:8888/api/sample',
  headers: {
    aaa1: 'testHeader1',
    aaa2: 'testHeader2',
    Cookie: 'bbb1=c1; bbb2=c2',
    Authorization: 'Bearer testToken99',
  },
  params: {
    test1: 'aaa',
    test2: 'bbb',
  },
  body: {
    log: 'value1',
    testValue: 'value2',
  },
  interceptors: {
    request: async (args) => {
      // 로딩바 생성
      args[0] = '/samples/test2'
      args[1].params.test2 = 'bbbb2';
      args[1].headers.aaa2 = 'testHeader2222';
      args[1].headers.Authorization = 'Bearer testToken88';
      return args;
    },
    response: async (response) => {
      // 로딩바 삭제
      if (response.status === 400) {
        return { test: '변경' };
      } else {
        return response;
      }
    },
  },
});
```

restApiUtil은 순수 fetch 함수를 확장한 자바스크립트 util class로서 next.js, nuxt.js 등 어떠한 자바스크립트 환경에서도 사용 가능한 util입니다. x2bee에서는 해당 함수를 next.js용으로 감싼 RestApi 유틸을 추가적으로 지원합니다.


# 3. Next.js용 공통 유틸 RestApi 사용

이 문서는 Next.js용 RestApi를 사용하여 데이터를 가져오는 방법에 대한 안내입니다.

Next.js에서는 Server component, Client component에 따라 restApiUtil의 사용법이 조금씩 달라집니다. 또한 Next.js 내에서 반복적으로 수행하는 공통 기능 등을 추가한 next(react)에 맞춰서 만들어진 RestApi Util을 제공합니다.

* 둘다 사이트(비즈니스 로직)에 맞춰서 공통화된 기능이 들어가 있음. (쿠키값에서 token을 가져와 추가 등)
* Server component의 내부적으로 Server Action 기능을 사용함
* Client component의 경우 내부적으로 React Hook 기능을 사용함

{% stepper %}
{% step %}

### Server Component

Server Component는 Promise를 반환하므로 then/catch 패턴이나 async/await 문법을 사용하여 처리합니다. 아래 예시는 async/await를 사용한 예입니다.

{% code title="app/page.tsx (Server Component 예시)" %}

```javascript
import { restApi } from '@/lib/common/plugins/restApi';

const Home = async () => {
  const response = await restApi.get('/api/display/v1/shop/1?dispMediaCd=20');
  console.log(response);

  return (
    <div>
      <h1>Home</h1>
    </div>
  );
};

export default Home;
```

{% endcode %}

Server Component에서는 내부적으로 Server Action 기능을 사용하도록 구현되어 있습니다.
{% endstep %}

{% step %}

### Client Component

Client Component에서는 React Hook을 사용하여 데이터를 가져옵니다. useEffect 내부나 이벤트 핸들러에서 async 함수를 호출하는 방식이 일반적입니다.

{% code title="app/page.tsx (Client Component 예시)" %}

```javascript
'use client';
import { useEffect } from 'react';
import { restApi } from '@/lib/common/plugins/restApi';

const Home = () => {
  useEffect(() => {
    const apiFunc = async () => {
      const response = await restApi.get('/api/display/v1/shop/1?dispMediaCd=20');
      console.log(response);
    };
    apiFunc();
  }, []);

  async function apitest() {
    const response = await restApi.get('/api/display/v1/shop/1?dispMediaCd=20');
    console.log(response);
  }

  return (
    <div>
      <h1>Home</h1>
      <button onClick={() => apitest()}>api test</button>
    </div>
  );
};

export default Home;
```

{% endcode %}

Client Component는 내부적으로 React Hook 기반 구현을 사용합니다.
{% endstep %}

{% step %}

### x2bee 표준 및 관리를 위한 api 작성 방법

<div align="left"><figure><img src="/files/Jqg59a5Vp16ZJmxQW2kN" alt="" width="347"><figcaption></figcaption></figure></div>

화면마다 개별적으로 API를 작성하기보다, 프로젝트 내에 api 디렉토리를 만들어 업무 또는 API별 파일로 분리해 작성하는 것을 권장합니다. 이렇게 하면 중복 코드를 줄이고 관련 API를 함께 관리하여 통일성을 유지하기 좋습니다.

예시:

{% code title="src/api/categoryApi.ts" %}

```javascript
import { restApi } from '@/lib/common/plugins/restApi';
import { DisplayCategory } from '@/types/display/category-data-model';
import { ResponseEntity } from '@/lib/common/plugins/restApi/restApiModel';

const CategoryApi = async (params?: { brandNo: string }) => {
  const response: ResponseEntity = (await restApi.get(
    '/api/display/v1/displayCategory',
    { params }
  )) as ResponseEntity;

  return (response.payload || []) as DisplayCategory[];
};

export default CategoryApi;
```

{% endcode %}

{% endstep %}
{% endstepper %}


# 05. State Management

이 글에서는 상태관리에 대한 개념과 종류 그리고 상태 관리를 위해 사용되는 Prop Drilling 방식과 주요한 상태 관리 라이브러리인 Zustand에 대한 기본적인 사용법을 안내하는 글입니다.

{% hint style="info" %}
‘상태관리’에서 상태란 “어플리케이션(통상 화면)에 영항을 끼치는 순수 자바스크립트 객체”라고 할 수 있습니다.

통상 ‘변화하는 데이터’를 일컫는데, SPA(Single Page Application)에서는 컴포넌트의 포함관계에 따라서 상태가 전파되고, 이에 따라 화면이 갱신되기 때문에 무척 중요한 관리 요소입니다.
{% endhint %}

상태관리에는 크게 세가지로 나뉩니다.

| **종류**        | **주요 특징**                                                                                                                                                                                  |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 지역 상태         | <p>- 특정 컴포넌트 안에서만 관리되는 상태를 뜻함.<br>- 다른 컴포넌트들과 데이터를 공유하지 않음.<br>- 예를 들면, input, selectbox 등에서 사용자의 입력값을 받는 경우. (보통 Form 데이터들이 지역상태에 속함.)</p>                                                |
| 컴포넌트간 상태      | <p>- 여러가지 컴포넌트에서 관리되는 상태.<br>- 다수의 컴포넌트에서 쓰이고, 또 영향을 미치는 상태를 뜻함 (프로젝트 곳곳에서 쓰이는 공통 컴포넌트 Modal을 예로 들 수 있음).<br>- 상위 컴포넌트에서 하위 컴포넌트로 prop을 넘겨 해당 컴포넌트까지 전달되도록 하는 Prop Drilling 방식을 필요로 함.</p> |
| 전역 상태         | <p>- 프로젝트 전체에 영향을 끼치는 상태. 예를 들면, User 기능을 생각하면 됨.<br>- 이 또한 Prop Drilling 방식을 활용해서 부모에서 자식으로 데이터를 전달.</p>                                                                                  |
| Prop Drilling | <p>- props를 오직 하위 컴포넌트로 전달하는 용도로만 쓰임.<br>- 컴포넌트에서 다른 컴포넌트로 데이터를 전달하는 과정(컴포넌트 > 중간 컴포넌트 > 중간 컴포넌트 > ... > 타겟 컴포넌트).<br>- prop 의 추적이 어려움</p>                                                 |

React에서 상태관리는 전통적으로 Context API, Redux가 많이 쓰였는데, 이해하는데 노력이 많이 들어가는게 단점이었습니다.

최근에는 쉽고 강력한 대안적인 상태관리 프레임워크가 많이 쓰이는데, 여기에는 Mobx, Recoil, Zustand 등이 많이 쓰이고 있습니다. 그 중에서 Zustand의 사용자가 급격히 늘어나는 추세입니다.

이번 장에서는 Zustand의 기본 사용법에 대해서 설명합니다.


# 1. Zustand

이 문서는 Zustand를 소개하고 간단한 예제 코드를 통해 기본적인 사용법을 설명하고 있습니다.

***

### Zustand

<figure><img src="/files/BaQlXjHLDhqVgtx1N4Vk" alt=""><figcaption></figcaption></figure>

Zustand는 간단하고 빠르며 확장성 있는 상태관리 솔루션입니다. hook를 기반으로 한 편리한 API를 가지고 있어서, 보일러플레이트 코드가 없습니다.

특히 Zustand를 사용하면 기존 React 동시성, 그리고 혼합랜더러 같은 곳에서 나타나는 컨텍스트 손실 같은 일반적인 문제들을 개선하는데 도움이 많이 됩니다.

그리 어렵지않으니, 예제 코드를 통해 사용법을 살펴보겠습니다.

{% stepper %}
{% step %}

### 1. 스토어 생성

다음은 Zustand로 상태 저장소(store)를 만드는 예제입니다.

{% code title="store.js" %}

```javascript
const useBearStore = create((set) => ({
  // 초기값이 0으로 설정된 bears라는 상태 속성
  bears: 0,
  // bears를 증가 시키는 함수
  // 'set' 함수를 사용하여 이전 상태를 가져와서 bears 값을 1 증가
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  // bears의 상태를 0으로 초기화
  removeAllBears: () => set({ bears: 0 }),
}))
```

{% endcode %}

설명:

* Zustand의 store는 기본적으로 hook입니다. 여기에는 원시값, 객체, 함수 등을 모두 넣을 수 있습니다.
* `set` 함수를 통해서 상태를 병합하고 업데이트합니다.
* 위 코드에서는 `create` 함수를 사용하여 `useBearStore`라는 상태관리 hook를 생성하고 있습니다. 이 함수는 상태 저장소를 만들기 위해 사용됩니다. 하나의 매개변수로 `set` 함수를 포함하는 콜백함수를 받아 상태를 업데이트합니다.
  {% endstep %}

{% step %}

### 2. 컴포넌트에서 사용

만든 `useBearStore`는 Provider 없이 어디서든 사용할 수 있습니다. 상태를 선택하면 컴포넌트는 변경 사항이 있을 때 다시 렌더링됩니다.

다음은 상태를 읽고 업데이트하는 예제 컴포넌트들입니다.

{% code title="BearCounter.jsx" %}

```jsx
function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here ...</h1>
}
```

{% endcode %}

{% code title="Controls.jsx" %}

```jsx
function Controls() {
  const increasePopulation = useBearStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}
```

{% endcode %}

설명:

* 예제 코드와 같이 생성된 `useBearStore`라는 hook를 사용하여 업데이트 함수를 가져와 화면에 뿌리기도 하고, 동작시키기도 할 수 있습니다.
* `BearCounter` 컴포넌트는 `useBearStore`를 호출하여 `bears` 상태를 렌더링하여 화면에 표시합니다.
* `Controls` 컴포넌트는 `increasePopulation`를 통해 버튼 클릭 시 상태를 변경합니다.
* 이처럼 Zustand의 특징 중 하나는 Provider를 사용하지 않아도 상태를 가져올 수 있다는 점입니다.
  {% endstep %}
  {% endstepper %}

***

<details>

<summary>문서 생성 정보</summary>

Document generated by Confluence on 2025-12-18 2:27 오전

[Atlassian](http://www.atlassian.com/)

</details>


# 2. x2beeStore

이 문서는 Next.js 프로젝트에서 상태를 관리하기 위한 x2beeStore를 사용하는 방법을 안내합니다.

첫번째 단계에서 파일을 생성하여 상태를 정의하고, 두번째 단계에서 정의한 상태를 저장하고 조회하는 방법을 안내합니다.

Zustand 사용법은 매우 간단하지만 상태를 생성할 때 반복되는 코드를 줄이기 위해 x2beeStore를 제공합니다.

## x2beeStore 사용방법

{% stepper %}
{% step %}

### 파일 생성 및 상태 정의

아래 코드는 `testStore.ts` 파일을 생성하고 x2beeStore를 사용하여 상태를 관리하는 예제입니다.

{% code title="testStore.ts" %}

```
```

{% endcode %}

```typescript
import x2beeStore from '@/lib/common/plugins/x2beeStore';

export const testState = {
  test1: '',
  test2: '',
  test3: '',
};

const useTestStore = x2beeStore('testStore', testState);

export default useTestStore;
```

위 코드와 같이 `x2beeStore`를 import하고, `testState`처럼 객체를 정의합니다.\
그리고 `x2beeStore` 함수를 사용하여 상태 저장소를 생성하고, 초기 상태로 `testState` 객체를 사용하여 `useTestStore` 변수에 할당합니다.
{% endstep %}

{% step %}

### 상태 저장 및 조회 예제

다음은 `testStore` 상태를 저장하고 조회하는 예제 컴포넌트입니다.

{% code title="TestPage.tsx" %}

```
```

{% endcode %}

```tsx
'use client';

import useTestStore from '@/lib/common/stores/testStore';

const TestPage = () => {
  function setStore() {
    useTestStore.setState({
      test1: '11111',
      test2: '22222',
      test3: '3333'
    });
  }

  function getStore() {
    const store = useTestStore.getState();
    console.log(store);
  }

  return (
    <>
      test2222
      <button onClick={() => setStore()}>상태 저장</button>
      <button onClick={() => getStore()}>상태 조회</button>
    </>
  );
};

export default TestPage;
```

`useTestStore`라는 이름으로 `testStore.ts` 파일에서 생성한 상태 저장소를 가져옵니다.\
`setStore`와 `getStore` 함수 예제를 바탕으로 상태를 저장하고 조회할 수 있습니다.
{% endstep %}
{% endstepper %}

{% hint style="info" %}

* 이 가이드는 x2beeStore(내부적으로 Zustand를 사용)를 간단히 사용하는 방법을 설명합니다.
* 실제 프로젝트에서는 타입 정의, 부분 업데이트, 미들웨어(예: persist) 적용 등을 추가로 고려하세요.
  {% endhint %}


# 3. RedisClient

이 문서는 서버 컴포넌트와 클라이언트 컴포넌트에서 레디스를 사용하여 데이터를 저장하고 조회하는 방법을 안내합니다.

서버 컴포넌트에서는 상태를 저장하거나 조회하기 어렵기 때문에 redisClient를 제공합니다.

{% stepper %}
{% step %}

### Server Component

다음 코드는 레디스를 서버 컴포넌트에서 작성하고 사용하는 예제입니다.

* getRedisValue와 setRedisValue 함수를 통해 데이터를 저장하고 가져옵니다.
* setRedisValue 함수에 Key, 객체, 유효시간(초)를 설정합니다. (예: 마지막 인자 500은 500초. 아무것도 넣지 않으면 무한으로 저장됨)
* getRedisValue 함수를 사용하여 레디스에서 Key에 해당하는 데이터를 가져와서 사용합니다.

{% code title="app/page.tsx (Server Component 예제)" %}

```
```

{% endcode %}

```javascript
import { restApi } from '@/lib/common/plugins/restApi';
import { getRedisValue, setRedisValue } from '@/lib/common/plugins/redisClient';

const SearchPage = async () => {
  const test = {
    test1: 'test1',
    test2: '999999',
    test3: 'weqweqweqwe',
  };

  // 마지막 500의 경우 500초로, 아무것도 안넣을 경우 무한으로 저장됨
  await setRedisValue('test998877', test, 500);

  const value = await getRedisValue('test998877');
  console.log(value);

  return (
    <>
      test1111
    </>
  );
};

export default SearchPage;
```

{% endstep %}

{% step %}

### Client Component

다음 코드는 클라이언트 측에서 레디스를 사용하는 예제입니다. 사용 방법과 설명은 Server Component에서와 동일합니다.

{% code title="app/page.client.tsx (Client Component 예제)" %}

```
```

{% endcode %}

```javascript
'use client';

import { restApi } from '@/lib/common/plugins/restApi';
import { useEffect, useState } from 'react';
import { getRedisValue, setRedisValue } from '@/lib/common/plugins/redisClient';

const TestPage = () => {
  async function setRedis() {
    const test = {
      test1: 'test12222',
      test2: '99999922222222',
      test3: 'weqweqweqwe222222',
    };
    await setRedisValue('test998877', test, 500);
  }

  async function getRedis() {
    const value = await getRedisValue('test998877');
    console.log(value);
  }

  return (
    <>
      test2222
      <button onClick={() => setRedis()}>레디스 저장</button>
      <button onClick={() => getRedis()}>레디스 조회</button>
    </>
  );
};

export default TestPage;
```

{% endstep %}
{% endstepper %}

다음과 같이 레디스 서버에 데이터가 저장된 것을 확인할 수 있습니다.

<figure><img src="/files/xdHmlvZYcHdB95TCmISE" alt="" width="563"><figcaption></figcaption></figure>


# 약어집

<table><thead><tr><th width="86.77783203125">No.</th><th width="125">용어</th><th width="134.666748046875">약어</th><th width="158.888916015625">영문</th><th>설명</th></tr></thead><tbody><tr><td>1</td><td>년차</td><td>YEAR_CNT</td><td></td><td></td></tr><tr><td>2</td><td>잔여</td><td>RMND</td><td></td><td></td></tr><tr><td>3</td><td>중</td><td>MID</td><td></td><td></td></tr><tr><td>4</td><td>ID</td><td>ID</td><td>identification</td><td>컴퓨터 통신에서 접속 사람 및 사물을 구분하는 식별번호</td></tr><tr><td>5</td><td>유입</td><td>INFW</td><td>inflow</td><td>물이 어떤 곳으로 흘러듦. 돈, 물품 따위의 재화가 들어옴</td></tr><tr><td>6</td><td>인터페이스</td><td>INF</td><td>interface</td><td>서로 다른 두 시스템, 장치, 소프트웨어 따위를 서로 이어 주는 부분</td></tr><tr><td>7</td><td>이전</td><td>PRE</td><td>PREVIOUS</td><td>1 오래전,옛날,기준시점보다 앞</td></tr><tr><td>8</td><td>수량</td><td>QTY</td><td>quantity</td><td>수효와 분량을 아울러 이르는 말</td></tr><tr><td>9</td><td>접수</td><td>ACCP</td><td>ACCEPT</td><td>신청이나 신고 따위를 구두(口頭)나 문서로 받음</td></tr><tr><td>10</td><td>접수자</td><td>ACPTMN</td><td>acceptance man</td><td>신청이나 신고 따위를 구두(口頭)나 문서로 받는 사람</td></tr><tr><td>11</td><td>매입사</td><td>ACQR</td><td>Acquirer</td><td>매입하는 회사나 업체</td></tr><tr><td>12</td><td>부속물</td><td>ACSR</td><td>ACCESSORIES</td><td>(어떤 기계나 기구의) 본체에 딸린 물건</td></tr><tr><td>13</td><td>계좌</td><td>ACTN</td><td>account</td><td>‘예금 계좌’의 준말.</td></tr><tr><td>14</td><td>추가</td><td>ADD</td><td>addition</td><td>나중에 더 보탬</td></tr><tr><td>15</td><td>주소</td><td>ADDR</td><td>address</td><td>사람이 살고 있는 곳이나 기관, 회사 따위가 자리 잡고 있는 곳을 행정 구역으로 나타낸 이름</td></tr><tr><td>16</td><td>정산</td><td>ADJ</td><td></td><td>매출 및 매출원가의 마감 및 확정 짓는 작업</td></tr><tr><td>17</td><td>성인</td><td>ADL</td><td>adult</td><td>자라서 어른이 된 사람. 보통 만 20세 이상의 남녀를 이른다</td></tr><tr><td>18</td><td>관리자</td><td>ADMIN</td><td>management man</td><td>어떤 일을 맡아 관할 처리하는 자</td></tr><tr><td>19</td><td>가감</td><td>ADST</td><td>adjustment</td><td>더하거나 더는 일. 또는 그렇게 하여 알맞게 맞추는 일</td></tr><tr><td>20</td><td>부가비용</td><td>ADTN</td><td>additional expense</td><td>표준 가격이나 계약 비용에 포함되지 않은 물자의 생산, 판매, 운반에 드는 잡비</td></tr><tr><td>21</td><td>홍보</td><td>ADVE</td><td>advertisement</td><td>널리 알림. 또는 그 소식이나 보도. 광고</td></tr><tr><td>22</td><td>사은행사</td><td>AE</td><td>appreciation event</td><td>받은 은혜를 갚기 위해 고객을 대상으로 선물 따위를 증정하는 행사.</td></tr><tr><td>23</td><td>담당자</td><td>AEMP</td><td>A EMPLOYERR</td><td>해당 업무를 수행하는 사람</td></tr><tr><td>24</td><td>후</td><td>AF</td><td>after</td><td>뒤나 다음</td></tr><tr><td>25</td><td>나이</td><td>AGE</td><td>AGE</td><td>사람이나 동ㆍ식물 따위가 세상에 나서 살아온 햇수.</td></tr><tr><td>26</td><td>약관</td><td>AGMT</td><td>agreement</td><td>계약의 당사자가 다수의 상대편과 계약을 체결하기 위하여 일정한 형식에 의하여 미리 마련한 계약의 내용</td></tr><tr><td>27</td><td>동의</td><td>AGR</td><td>AGREEMENT</td><td>같은 뜻. 또는 뜻이 같음. 의사나 의견을 같이함.</td></tr><tr><td>28</td><td>약정</td><td>AGRM</td><td>aggrement</td><td>어떤 일을 약속하여 정함.</td></tr><tr><td>29</td><td>앳홈</td><td>AHME</td><td>AT HOME</td><td>고객의 가정에 직접 방문하여 제공하는 서비스( 배송/수거 등)</td></tr><tr><td>30</td><td>허용</td><td>ALN</td><td>allowance</td><td>허락하여 너그럽게 받아들임</td></tr><tr><td>31</td><td>금액</td><td>AMT</td><td>amount</td><td>돈의 액수. ≒금원(金 員).</td></tr><tr><td>32</td><td>답변</td><td>ANS</td><td>answer</td><td>물음에 대하여 밝혀 대답함</td></tr><tr><td>33</td><td>기념일</td><td>ANVY</td><td>Anniversary</td><td>축하하거나 기릴 만한 일이 있을 때, 해마다 그 일이 있었던 날을 기억하는 날</td></tr><tr><td>34</td><td>부가</td><td>ANX</td><td>annex</td><td>주된 것에 덧붙임</td></tr><tr><td>35</td><td>API</td><td>API</td><td>APPLICATION PROGRAMMING INTERFACE</td><td>응용프로그램을 개발하기 위한 함수의 집합</td></tr><tr><td>36</td><td>부연</td><td>APL</td><td>amplification</td><td>이해하기 쉽도록 설명을 덧붙여 자세히 말함</td></tr><tr><td>37</td><td>적용</td><td>APLY</td><td>APPLY</td><td>알맞게 이용하거나 맞추어 씀</td></tr><tr><td>38</td><td>앱</td><td>APP</td><td>MOBILE APPLICATION</td><td>응용 기술 (프로그램)을 뜻하는 애플리케이션 (Application)의 줄임말로, 특정 목적을 가지고 제작된 프로그램</td></tr><tr><td>39</td><td>승인자</td><td>APRMN</td><td>approve man</td><td>승인요청에 대한 승인 및 반려를 수행하는 사람</td></tr><tr><td>40</td><td>승인</td><td>APRV</td><td>approve</td><td>작업을 수행할 수 있도록 허가하는 행위</td></tr><tr><td>41</td><td>결재자</td><td>APRVMN</td><td>approve man</td><td>부하가 제출한 의안을 헤아려 승인하는 사람</td></tr><tr><td>42</td><td>품목</td><td>ARTC</td><td>ARTICLE</td><td>물품의 이름을 쓴 목록, 물품 종류의 이름</td></tr><tr><td>43</td><td>연관</td><td>ASCT</td><td>association</td><td>사물이나 현상이 일정한 관계를 맺는 일</td></tr><tr><td>44</td><td>자산</td><td>AST</td><td>ASSETS</td><td>개인이나 법인이 소유하고 있는 경제적 가치가 있는 유형ㆍ무형의 재산</td></tr><tr><td>45</td><td>첨부</td><td>ATCH</td><td>attach</td><td>안건이나 문서 따위를 덧붙임</td></tr><tr><td>46</td><td>이후</td><td>ATF</td><td>after</td><td>기준이 되는 때를 포함하여 그보다 뒤.</td></tr><tr><td>47</td><td>유의</td><td>ATND</td><td>ATTEND</td><td>(留意)마음에 둠. 잊지 않고 새겨 둠</td></tr><tr><td>48</td><td>속성</td><td>ATT</td><td>attribute</td><td>사물의 특징이나 성질. 사물의 현상적 성질</td></tr><tr><td>49</td><td>주의</td><td>ATTD</td><td></td><td>특별한 사항에 대한 경계나 주목</td></tr><tr><td>50</td><td>자동</td><td>AUTO</td><td>auto</td><td>기계나 설비 따위가 자체 내에 있는 일정한 장치의 작용에 의하여 스스로 작동함</td></tr><tr><td>51</td><td>평균</td><td>AVG</td><td>average</td><td>여러 사물의 질이나 양 따위를 통일적으로 고르게 한 것, 여러 수나 같은 종류의 양의 중간 값을 갖는 수</td></tr><tr><td>52</td><td>아바타</td><td>AVTA</td><td>avatar</td><td>이른바 이라고 불리는 사이버상의 캐릭터를 이르는 말</td></tr><tr><td>53</td><td>잔액</td><td>BAL</td><td>BALANCE</td><td>금액이나 물품에서 일정한 액수나 양을 뺀 나머지. 또는 전체 매출(賣出)에서 수지(收支) 또는 대차(貸借)를 뺀 나머지</td></tr><tr><td>54</td><td>은행</td><td>BANK</td><td>bank</td><td>예금을 받아 그 돈을 자금으로 하여 대출, 어음 거래, 증권의 인수 따위를 업무로 하는 금융 기관</td></tr><tr><td>55</td><td>바코드</td><td>BCODE</td><td>bar code</td><td>물건을 관리하기 위한 특수기호</td></tr><tr><td>56</td><td>기본</td><td>BASE</td><td>basis</td><td>사물·현상·이론·시설 따위의 기초와 근본</td></tr><tr><td>57</td><td>근거</td><td>BASS</td><td>basis</td><td>어떤 일이나 의논, 의견에 그 근본이 됨. 또는 그런 까닭.</td></tr><tr><td>58</td><td>BATCH</td><td>BATCH</td><td>BATCH JOB</td><td>스케쥴링에 의해 정해진 시간에 서버에서 자동으로 실행시키는 작업</td></tr><tr><td>59</td><td>게시</td><td>BB</td><td>notice</td><td>여러 사람에게 알리기 위하여 내붙이거나 내걸어 두루 보게 함</td></tr><tr><td>60</td><td>게시판</td><td>BBS</td><td>notice board</td><td>게시하는 글·그림·사진 따위를 붙이는 판(板)</td></tr><tr><td>61</td><td>게시글</td><td>BBC</td><td></td><td>여러 사람이 볼 수 있도록 인터넷 게시판에 올린 글</td></tr><tr><td>62</td><td>방송</td><td>BD</td><td>broadcasting</td><td>라디오나 텔레비전을 통하여 널리 듣고 볼 수 있도록 음성이나 영상을 전파로 내보내는 일</td></tr><tr><td>63</td><td>베스트</td><td>BEST</td><td>best</td><td>여러것 중에서 가장 좋은것에 대한 외래어</td></tr><tr><td>64</td><td>권</td><td>BIL</td><td>BILL</td><td>지폐나 수표의 단위 액수에 붙어, 얼마짜리임을 나타내는 말. / 일정한 자격이나 권리를 증명하는 표임을 나타내는 말.</td></tr><tr><td>65</td><td>계산서</td><td>BILL</td><td>bill</td><td>계산에 관한 것을 명기한 서류</td></tr><tr><td>66</td><td>출신</td><td>BIRT</td><td>birth place</td><td>출생 당시 가정이 속하여 있던 사회적 신분</td></tr><tr><td>67</td><td>사업</td><td>BIZ</td><td>business</td><td>어떤 일을 일정한 목적과 계획을 가지고 짜임새 있게 지속적으로 경영함</td></tr><tr><td>68</td><td>영업외</td><td>BIZEX</td><td>non business</td><td>영업이외</td></tr><tr><td>69</td><td>배경</td><td>BKGR</td><td>BACKGROUND</td><td>사진이나 그림 등에서 그 주요 제재(題材) 뒤편에 펼쳐진 부분</td></tr><tr><td>70</td><td>업종</td><td>BKIND</td><td>BKIND</td><td>직업이나 영업의 종류</td></tr><tr><td>71</td><td>블랙리스트</td><td>BLKL</td><td>BLACK LIST</td><td>위험 분류 대상</td></tr><tr><td>72</td><td>빌링</td><td>BLLG</td><td>BILLING</td><td>요금 청구</td></tr><tr><td>73</td><td>사업자</td><td>BMAN</td><td>BUSINESS MAN</td><td>사업자등록을 하고 경제활동에 종사하는 사람</td></tr><tr><td>74</td><td>블라인드</td><td>BLND</td><td>BLIND</td><td>창문에 달아 햇빛을 가리는 물건</td></tr><tr><td>75</td><td>배너</td><td>BNR</td><td>banner</td><td>인터넷 홈페이지에 띠 모양으로 만들어 부착하는 이미지</td></tr><tr><td>76</td><td>합포장</td><td>BPCK</td><td>bundle packing</td><td>물품을 수송 ·보관함에 있어서 가치 및 상태를 보호하기 위하여 적절한 재료나 용기 등을 물품에 시장(施裝)하는 기술 및 상태.</td></tr><tr><td>77</td><td>브랜드</td><td>BRAND</td><td>brand</td><td>사업자가 자기 상품에 대하여, 경쟁업체의 것과 구별하기 위하여 사용하는 기호ㆍ문자ㆍ도형 따위의 일정한 표지</td></tr><tr><td>78</td><td>지점</td><td>BRCH</td><td>branch</td><td>본점에서 갈린 가게</td></tr><tr><td>79</td><td>간략</td><td>BREF</td><td>BRIEF</td><td>간단하고 짤막</td></tr><tr><td>80</td><td>생년월일</td><td>BRTH</td><td>birthday</td><td>세상에 태어난 날. 또는 태어난 날을 기리는 해마다의 그날</td></tr><tr><td>81</td><td>장바구니</td><td>BSKET</td><td>shopping basket</td><td>구매하고자 하는 상품을 모아놓은 공간</td></tr><tr><td>82</td><td>최고</td><td>BST</td><td>BEST</td><td>으뜸인 것. 또는 으뜸이 될 만한 것</td></tr><tr><td>83</td><td>버튼</td><td>BTN</td><td>BOTTON</td><td>[전기·전자 ] 전기 장치에 전류를 끊거나 이어 주거나 하며 기기를 조작하는 장치. 옷 따위의 두 폭이나 두 짝을 한데 붙였다 떼었다 하는, 옷고름이나 끈 대신으로 쓰는 물</td></tr><tr><td>84</td><td>업태</td><td>BTYP</td><td>BTYP</td><td>영업이나 사업의 실태</td></tr><tr><td>85</td><td>구매</td><td>PUR</td><td>PURCHASE</td><td>사들임.구입</td></tr><tr><td>86</td><td>매입</td><td>BUY</td><td>BUY</td><td>물품 따위를 사들임</td></tr><tr><td>87</td><td>카드</td><td>CARD</td><td>card</td><td>일정한 크기로 조그맣게 자른 두꺼운 종이나 플라스틱. 어떤 내용을 증명하는 역할을 한다</td></tr><tr><td>88</td><td>카드사</td><td>CARDCO</td><td>card company</td><td>카드 회사의 줄임말</td></tr><tr><td>89</td><td>캐시</td><td>CASH</td><td>CASH</td><td>인터넷 사용자가 자주 찾는 정보를 따로 모아두는 서버</td></tr><tr><td>90</td><td>사유</td><td>CAUS</td><td></td><td>어떠한 결론이나 결과에 이른 까닭이나 근거</td></tr><tr><td>91</td><td>정답</td><td>CAWR</td><td>CORRECT ANSWER</td><td>옳은 답, 바른 답</td></tr><tr><td>92</td><td>고객상담</td><td>CCN</td><td>customer counseling</td><td>고객들의 편의를 제공하기 위하여 문제를 해결하거나 궁금증을 풀기 위해 서로 의논함</td></tr><tr><td>93</td><td>코드</td><td>CD</td><td>CODE</td><td>정보를 나타내기 위한 기호 체계. 데이터 코드, 기능 코드, 오류를 검사하기 위한 검사 코드 따위가 있다.</td></tr><tr><td>94</td><td>휴대폰</td><td>CELL</td><td>CELL</td><td>이동 가능한 개인형 전화</td></tr><tr><td>95</td><td>인증</td><td>CERTI</td><td>certification</td><td>어떠한 문서나 행위가 정당한 절차로 이루어졌다는 것을 공적 기관이 증명함</td></tr><tr><td>96</td><td>확정</td><td>CFM</td><td>CONFIRMATION</td><td>일을 확실하게 정함</td></tr><tr><td>97</td><td>변경</td><td>CHG</td><td>CHANGE</td><td>다르게 바꾸어 새롭게 고침.</td></tr><tr><td>98</td><td>채널</td><td>CHL</td><td>channel</td><td>정보의 발생원으로부터 수요처에 이르는 선로와 장비들을 포함하는 기능적인 접속 회로</td></tr><tr><td>99</td><td>검수자</td><td>CHMN</td><td>check man</td><td>납품된 자재에 대한 품질 및 수량을 점검하는 사람</td></tr><tr><td>100</td><td>부담</td><td>BRDN</td><td>BURDEN</td><td>(일정한 책임이나 일을) 부담하여 맡게 하는 것.</td></tr><tr><td>101</td><td>유료</td><td>CHRG</td><td>charge</td><td>요금을 내게 되어 있음</td></tr><tr><td>102</td><td>가슴</td><td>CHST</td><td>chest</td><td>가슴(신체의 일부)</td></tr><tr><td>103</td><td>CI</td><td>CI</td><td>Connecting Information</td><td>본인 확인기관 등에서 개인별로 고유하게 부여하는 개인 식별정보로 어느 업체에서 발급해도 유일하게 발급되므로 이 값이 같으면 동일인으로 판단할 수 있다</td></tr><tr><td>104</td><td>주민세</td><td>CITX</td><td>citizen tax</td><td>지방세의 하나. 그 지역에 거주하는 개인과 그 지역에 사무소나 사업소를 둔 법인, 또는 그들의 소득에 대하여 부과한다</td></tr><tr><td>105</td><td>폐점</td><td>CLBR</td><td>CLOSED BRANCH</td><td>폐쇄된 점</td></tr><tr><td>106</td><td>협조</td><td>CLBT</td><td>collaboration, cooperation</td><td>힘을 합하여 서로 도움</td></tr><tr><td>107</td><td>클릭</td><td>CLCK</td><td>CLICK</td><td>컴퓨터에서, 마우스의 단추를 누름, 또는 그런 행위</td></tr><tr><td>108</td><td>청구</td><td>CLM</td><td></td><td>상대방에 대하여 일정한 행위를 요구하는 일.</td></tr><tr><td>109</td><td>클리닝</td><td>CLNG</td><td>cleaning</td><td>웹평가단의 평가지 작성내용중 불량응답정보</td></tr><tr><td>110</td><td>컬러</td><td>CLOR</td><td>color</td><td>빛깔이 있는 것</td></tr><tr><td>111</td><td>마감</td><td>CLOSE</td><td>CLOSE</td><td>종결, 종료</td></tr><tr><td>112</td><td>분류</td><td>CAT</td><td>CATEGORY</td><td>구분을 위해 정해진 규칙에 의해 나누는 일</td></tr><tr><td>113</td><td>공통</td><td>CM</td><td>commonness</td><td>둘 또는 그 이상의 여럿 사이에 두루 통하고 관계됨</td></tr><tr><td>114</td><td>통합</td><td>CMBN</td><td>combine</td><td>둘 이상의 조직이나 기구 등을 하나로 합치는 것</td></tr><tr><td>115</td><td>명령어</td><td>CMDW</td><td>COMMNAD WORD</td><td>컴퓨터에 연산이나 일정한 동작을 명령하는 기계어</td></tr><tr><td>116</td><td>캠페인</td><td>CMPG</td><td>CAMPAIGN</td><td>사회적·정치적 목적을 위하여 조직적·계속적으로 어떤 주의·주장을 알리고 따르게 하는 운동</td></tr><tr><td>117</td><td>구성</td><td>CMPS</td><td>COMPOSTITUTION</td><td>몇 가지 요소를 조립하여 하나로 만드는 일. 또는 그 결과</td></tr><tr><td>118</td><td>수수료</td><td>CMSN</td><td>COMMISSION</td><td>일의 대가에 대해 금전으로 지급되는 보상</td></tr><tr><td>119</td><td>화장품</td><td>CMTS</td><td>cosmetics</td><td>화장하는 데 쓰는 크림, 분, 향수 따위를 통틀어 이르는 말.</td></tr><tr><td>120</td><td>취소</td><td>CNCL</td><td>CANCEL</td><td>일단 유효하게 성립한 행위의 효력을 소급하여 소멸하는 의사 표시</td></tr><tr><td>121</td><td>해지</td><td>CNCEL</td><td>cancel</td><td>계약 당사자 한쪽의 의사 표시에 의하여 계약에 기초한 법률관계를 말소하는 것.</td></tr><tr><td>122</td><td>취소자</td><td>CNCLMN</td><td>cancel man</td><td>취소하는 사람</td></tr><tr><td>123</td><td>연락</td><td>CNCT</td><td>contact</td><td>어떤 사실을 상대편에게 알림.</td></tr><tr><td>124</td><td>소비자</td><td>CNMR</td><td>consumer</td><td>재화를 소비하는 사람을 나타냄</td></tr><tr><td>125</td><td>상담</td><td>CNSL</td><td>counseling</td><td>회사의 통신서비스를 이용하는 고객이 서비스 이용문의를 하거나, 고객사유로 발생한 장애로 인해 서비스 이용에 불편이 있는 경우, 조치하여 정상적으로 이용할 수 있도록 하는 업무</td></tr><tr><td>126</td><td>상담원</td><td>CNSR</td><td>counselor</td><td>개인의 생활이나 적응 문제 따위에 관하여 개별적으로 지도하고 조언하는 사람</td></tr><tr><td>127</td><td>건수</td><td>CNT</td><td>count</td><td>사물이나 사건의 가짓수</td></tr><tr><td>128</td><td>연속</td><td>CNTN</td><td>continuity</td><td>끊어지지 않고 계속 이어지거나 지속됨</td></tr><tr><td>129</td><td>센터</td><td>CNTR</td><td>center</td><td>그것을 파는 곳을 나타내는 말. 그 일을 담당하는 곳을 나타내는 말</td></tr><tr><td>130</td><td>국가</td><td>CNTRY</td><td>COUNTRY</td><td>일정한 영토를 보유하며, 거기 사는 국민들로 구성되고, 하나의 통치 조직을 가진 집단</td></tr><tr><td>131</td><td>업체배송</td><td>CODLV</td><td>cooperation company delivery</td><td>직매입상품이아닌, 협력사에 존재하는 상품을 협력사가 직접 지정한 택배사를 통해 고객에게 배송함</td></tr><tr><td>132</td><td>출입</td><td>COGO</td><td>COMING AND GOING</td><td>드나드는일</td></tr><tr><td>133</td><td>요리사</td><td>COKR</td><td>cook</td><td>요리를 전문으로 하는 사람.</td></tr><tr><td>134</td><td>칼럼</td><td>COL</td><td>column</td><td>신문, 잡지 따위의 특별 기고. 또는 그 기고란.</td></tr><tr><td>135</td><td>색상</td><td>COLOR</td><td>COLOR</td><td>색을 빨강, 노랑, 파랑 따위로 구분하게 하는, 색 자체가 갖는 고유의 특성</td></tr><tr><td>136</td><td>컬렉션</td><td>COLT</td><td>COLLATION</td><td>의류 브랜드나 디자이너들이 일정한 시즌이 시작하기 전에 작품을 선보이려고 여는 패션 발표회</td></tr><tr><td>137</td><td>회사</td><td>COM</td><td>COMPANY</td><td>상행위 또는 그 밖의 영리 행위를 목적으로 하는 사단 법인. 주식회사, 유한 회사, 합자 회사, 합명 회사의 네 가지가 있다</td></tr><tr><td>138</td><td>공동</td><td>COMN</td><td></td><td>둘 이상의 사람이나 단체가 함께 일을 하거나, 같은 자격으로 관계를 가짐</td></tr><tr><td>139</td><td>비교</td><td>COMPA</td><td>compare</td><td>둘 이상의 사물을 견주어 서로 간의 유사점, 차이점, 일반 법칙 따위를 고찰하는 일</td></tr><tr><td>140</td><td>조건</td><td>COND</td><td>condition</td><td>어떤 일을 이루게 하거나 이루지 못하게 하기 위하여 갖추어야 할 상태나 요소, 일정한 일을 결정하기에 앞서 내놓는 요구나 견해</td></tr><tr><td>141</td><td>확인</td><td>CONF</td><td>confirm</td><td>틀림없이 그러한가를 알아보거나 인정함. 또는 그런 인정, 특정한 사실이나 법률관계의 존속, 폐지를 판단하여 인정함</td></tr><tr><td>142</td><td>접속</td><td>CONN</td><td>connect</td><td>서로 맞대어 이음,컴퓨터등에 연결함</td></tr><tr><td>143</td><td>코너</td><td>CONR</td><td>CONER</td><td>백화점 따위의 큰 상가에서 특정한 상품을 진열하고 팔기 위한 곳. 어떤 일이나 상황이 헤쳐 나가기 어렵고 곤란하게 된 상태를 비유적으로 이르는 말.</td></tr><tr><td>144</td><td>내용</td><td>CONTS</td><td>CONTENTS</td><td>말, 글, 그림, 연출 따위의 모든 표현 매체 속에 들어 있는 것. 또는 그런 것들로 전하고자 하는 것</td></tr><tr><td>145</td><td>전환</td><td>CONVN</td><td>conversion</td><td>다른 방향이나 상태로 바뀌거나 바꿈</td></tr><tr><td>146</td><td>제휴사</td><td>COOPCM</td><td>cooperation company</td><td>행동을 함께하기 위하여 서로 붙들어 도와주는 회사</td></tr><tr><td>147</td><td>제휴</td><td>COOPER</td><td>COOPERATION</td><td>행동을 함께하기 위하여 서로 붙들어 도와줌</td></tr><tr><td>148</td><td>법인</td><td>CORPN</td><td>corporation</td><td>자연인이 아니면서 법에 의하여 권리 능력이 부여되는 사단과 재단</td></tr><tr><td>149</td><td>비용</td><td>COST</td><td>cost</td><td>상품 또는 용역을 얻기 위한 자금 등의 지출</td></tr><tr><td>150</td><td>쿠폰북</td><td>CPBOOK</td><td>cupon book</td><td>할인권이나 이용권 따위를 한데 모아 엮은 책</td></tr><tr><td>151</td><td>용량</td><td>CPCT</td><td>CAPACITY</td><td>용기 안에 들어갈 수 있는 물건의 분량</td></tr><tr><td>152</td><td>쿠폰</td><td>CPN</td><td>COUPON</td><td>한 장씩 떼어서 쓰게 되어 있는 표.</td></tr><tr><td>153</td><td>보상</td><td>CPNS</td><td></td><td>남에게 진 빚이나 받은 물건을 갚는 것</td></tr><tr><td>154</td><td>현금영수증</td><td>CR</td><td>CASH RECEIPT</td><td>현금결제에 대하여 연말 소득공제 혜택 부여하도록 법으로 규정되기때문에 고객이 현금결제시 자동으로 발급되는 영수증</td></tr><tr><td>155</td><td>통화</td><td>CRC</td><td>CURRENCY</td><td>현금으로서의 화폐와 요구불 예금을 합쳐 이르는 말. 전자를 ‘현금 통화’, 후자를 ‘예금 통화’로 구분하여 부르기도 함</td></tr><tr><td>156</td><td>크리에이터</td><td>CRETR</td><td>creator</td><td>새로운 광고를 처음으로 만들어 내는 사람.</td></tr><tr><td>157</td><td>기표</td><td>CRNS</td><td>CREATION OF NEW SLIP</td><td>전표를 생성함</td></tr><tr><td>158</td><td>기표자</td><td>CRNSMN</td><td>CREATE PERSON OF NEW SLIP</td><td>전표를 생성하는 사람</td></tr><tr><td>159</td><td>단면</td><td>CRSCT</td><td>CROSS SECTION</td><td>물체의 잘라 낸 면</td></tr><tr><td>160</td><td>이월</td><td>CROV</td><td>carryover</td><td>옮기어 넘김</td></tr><tr><td>161</td><td>앞밑위</td><td>CRTH</td><td>CROTCH</td><td>지퍼 아래 십자선에서 앞으로 벨트를 매는 곳의 맨 윗부분까지의 길이</td></tr><tr><td>162</td><td>CS</td><td>CS</td><td>customer service</td><td>customer service</td></tr><tr><td>163</td><td>연계</td><td>CTAC</td><td>contact</td><td>어떤 일이나 사람과 관련하여 관계를 맺음. 또는 그 관계</td></tr><tr><td>164</td><td>카테고리</td><td>CTG</td><td>category</td><td>카테고리</td></tr><tr><td>165</td><td>제어</td><td>CTRL</td><td>control</td><td>억눌러 따르게 함, 기계·설비 따위가 알맞게 움직이도록 조절함</td></tr><tr><td>166</td><td>고객</td><td>CUST</td><td>customer</td><td>모든 가입고객(회원, 비회원) 상품을 청약하여 비용을 지불하고 사용하는 법인 또는 개인</td></tr><tr><td>167</td><td>주기</td><td>CYCLE</td><td>cycle</td><td>같은 현상이나 특징이 한 번 나타나고부터 다음 번 되풀이되기까지의 기간</td></tr><tr><td>168</td><td>일별</td><td>DAYCL</td><td>each day</td><td>날을 단위로 나눈 구별</td></tr><tr><td>169</td><td>할인</td><td>DC</td><td>discount</td><td>일정한 값에서 얼마를 뺌</td></tr><tr><td>170</td><td>신고</td><td>DCL</td><td></td><td>기관이나 조직체의 구성원이 윗사람에게 어떤 사실을 보고하거나 알리는 일</td></tr><tr><td>171</td><td>일</td><td>DD</td><td>DAY</td><td>하루 동안</td></tr><tr><td>172</td><td>기일</td><td>DDAY</td><td>DDAY</td><td>정해진 날짜.</td></tr><tr><td>173</td><td>일수</td><td>DDS</td><td>THE NUMBER OF DAYS</td><td>일의 수</td></tr><tr><td>174</td><td>취급</td><td>DEAL</td><td>DEALING</td><td>1 물건을 사용하거나 소재나 대상으로 삼음. 2 사람이나 사건을 어떤 태도로 대하거나 처리함. '다룸'으로 순화.</td></tr><tr><td>175</td><td>삭제</td><td>DEL</td><td>delete</td><td>깎아 없애거나 지워 버림</td></tr><tr><td>176</td><td>배송</td><td>DELI</td><td>DELIVERY</td><td>배달(配達)과 발송(發送).</td></tr><tr><td>177</td><td>예금주</td><td>DEPOSITOR</td><td>depositor</td><td>금융 기관에 돈을 맡긴 사람</td></tr><tr><td>178</td><td>부서</td><td>DEPT</td><td>department</td><td>업무수행을 위해 조직되어진 조직단위</td></tr><tr><td>179</td><td>설명</td><td>DESC</td><td>description</td><td>어떤 일이나 대상의 내용을 상대편이 잘 알 수 있도록 밝혀 말함</td></tr><tr><td>180</td><td>세부</td><td>DETL</td><td>detail</td><td>자세한 부분</td></tr><tr><td>181</td><td>전시</td><td>DISP</td><td>display</td><td>여러 가지 물품을 한곳에 벌여 놓고 보임</td></tr><tr><td>182</td><td>배분</td><td>DIV</td><td>distribution</td><td>몫몫이 별러 나눔. 분배</td></tr><tr><td>183</td><td>구분자</td><td>DIVOBJ</td><td>DISTINGUISHER</td><td>데이터의 항목을 구분하여 편성하는 표지</td></tr><tr><td>184</td><td>배송비</td><td>DLEX</td><td>delevery amount</td><td>배달(配達)과 발송(發送)에 대한 비용</td></tr><tr><td>185</td><td>배송지</td><td>DLVP</td><td>delevery place</td><td>배송 장소</td></tr><tr><td>186</td><td>DM</td><td>DM</td><td>direct mail</td><td>상품 등의 광고나 선전을 위해서 특정 고객층 앞으로 직접 우송하는 서신·카탈로그 등의 인쇄물.</td></tr><tr><td>187</td><td>다운로드</td><td>DNLD</td><td>download</td><td>시스템에서 파일을 받아 오는 것</td></tr><tr><td>188</td><td>문서</td><td>DOC</td><td>document</td><td>글이나 기호로써 일정한 의사나 관념 또는 사상을 표시한 것</td></tr><tr><td>189</td><td>국내</td><td>DOM</td><td>Domestic</td><td>나라의 안</td></tr><tr><td>190</td><td>도메인</td><td>DOMAIN</td><td>domain</td><td>사업 활동의 영역. 본업. 관계형 데이터베이스에서 테이블의 각 속성이 가질 수 있는 값의 집합.</td></tr><tr><td>191</td><td>동</td><td>DONG</td><td>dong</td><td>동네이름 동</td></tr><tr><td>192</td><td>입금자</td><td>DPMN</td><td>deposit man</td><td>은행 따위에 예금하거나 빚을 갚기 위하여 돈을 들여놓는 일을 하는 자</td></tr><tr><td>193</td><td>입금</td><td>DPT</td><td>deposit</td><td>은행 따위에 예금하거나 빚을 갚기 위하여 돈을 들여놓는 일</td></tr><tr><td>194</td><td>깊이</td><td>DPTH</td><td>depth</td><td>깊이, 순서등의 뎁쓰</td></tr><tr><td>195</td><td>불일치</td><td>DSCD</td><td>DISCORD</td><td>일치하지 아니함</td></tr><tr><td>196</td><td>발신자</td><td>DSMN</td><td>dispatch man</td><td>소식이나 우편 또는 전신을 보내는 자</td></tr><tr><td>197</td><td>발신</td><td>DSP</td><td>dispatch of a message</td><td>소식이나 우편 또는 전신을 보냄. 또는 그런 것</td></tr><tr><td>198</td><td>파기</td><td>DSTR</td><td>DESTROY</td><td>깨뜨리거나 찢어서 내버림</td></tr><tr><td>199</td><td>일자</td><td>DT</td><td>DAYS</td><td>날의 수</td></tr><tr><td>200</td><td>내역</td><td>HSTRY</td><td>HISTORY</td><td>물품이나 금액 따위의 내용.</td></tr><tr><td>201</td><td>상세</td><td>DTL</td><td>detail</td><td>자세히 나타냄</td></tr><tr><td>202</td><td>일시</td><td>DTM</td><td>datetime</td><td>어느 한 시기의 짧은 동안에</td></tr><tr><td>203</td><td>중복</td><td>DUP</td><td></td><td>둘 이상의 수</td></tr><tr><td>204</td><td>직무</td><td>DUTY</td><td>duty</td><td>직책이나 직업상에서 책임을 지고 담당하여 맡은 사무.</td></tr><tr><td>205</td><td>디바이스</td><td>DVC</td><td>DEVICE</td><td>어떤 특정한 목적을 위하여 구성한 기계적ㆍ전기적ㆍ전자적인 장치</td></tr><tr><td>206</td><td>전용</td><td>DVSN</td><td>DIVERSION</td><td>예정되어 있는 곳에 쓰지 않고 다른 데로 돌려서 쓰는 것</td></tr><tr><td>207</td><td>편집</td><td>EDT</td><td>editing</td><td>일정한 방침 아래 여러 가지 재료를 모아 신문, 잡지, 책 따위를 만드는 일</td></tr><tr><td>208</td><td>이기프트</td><td>EGIFT</td><td>E GIFT</td><td>전자 상품권</td></tr><tr><td>209</td><td>이메일</td><td>EMAIL</td><td>electronic mail</td><td>컴퓨터 통신망을 통해서 메시지를 전송하는 것, 또는 전송된 메시지. 전자우편이라고도 함</td></tr><tr><td>210</td><td>긴급</td><td>EMERG</td><td>emergency</td><td>긴요하고 급함</td></tr><tr><td>211</td><td>사원</td><td>EMP</td><td>employee</td><td>회사에서 근무하는 사람.</td></tr><tr><td>212</td><td>종료</td><td>END</td><td>end</td><td>어떤 행동이나 일 따위를 끝마침</td></tr><tr><td>213</td><td>영문</td><td>ENG</td><td>english</td><td>영어로 쓴 글</td></tr><tr><td>214</td><td>업체</td><td>ENTP</td><td>a business enterprise</td><td>사업이나 기업의 주체</td></tr><tr><td>215</td><td>협력사</td><td>ENTR</td><td>ENTERPRISE</td><td>힘을 합하여 서로 도와주는 회사</td></tr><tr><td>216</td><td>ERP</td><td>ERP</td><td>ENTERPRISE RESOURCE PLANNING</td><td>기업 전반의 업무 프로세스를 통합적으로 관리, 경영상태를 실시간으로 파악하고 정보를 공유하게 함으로써 빠르고 투명한 업무처리의 실현을 목적으로 한 기업 경쟁력 강화 역할을 하는 통합 정보 시스템</td></tr><tr><td>217</td><td>에스크로</td><td>ESCR</td><td>ESCROW</td><td>공신력있는 제3자가 소비자의 결제대금을 예치하고 있다가 상품배송이 정상적으로 완료된 후, 그 대금을 지급받는 방식</td></tr><tr><td>218</td><td>기타</td><td>ETC</td><td>etc</td><td>그외, 등등</td></tr><tr><td>219</td><td>평가</td><td>EVLT</td><td>evaluate</td><td>사물의 가치나 수준 따위를 평함. 또는 그 가치나 수준</td></tr><tr><td>220</td><td>이벤트</td><td>EVT</td><td>event</td><td>불특정의 사람들을 모아 놓고 개최하는 잔치. ‘사건’, ‘행사’로 순화</td></tr><tr><td>221</td><td>행사</td><td>EVET</td><td>EVENT</td><td>어떤 일을 시행함</td></tr><tr><td>222</td><td>교환</td><td>EXCH</td><td>exchange</td><td>교체와는 다른 의미로 스위치의 의미</td></tr><tr><td>223</td><td>제외</td><td>EXCP</td><td>exception</td><td>따로 떼어 내어 한데 헤아리지 않음</td></tr><tr><td>224</td><td>예외</td><td>EXCPT</td><td>except</td><td>일반적 규칙이나 정례에서 벗어나는 일</td></tr><tr><td>225</td><td>실행</td><td>EXE</td><td>execute</td><td>실제로 행함</td></tr><tr><td>226</td><td>노출</td><td>EXP</td><td>EXPOSUR</td><td>겉으로 드러나거나 드러냄</td></tr><tr><td>227</td><td>보기</td><td>EXPL</td><td>example of question</td><td>어떤 사실을 설명하거나 증명하기 위하여 내세워 보이는 대표적인 것.</td></tr><tr><td>228</td><td>실패</td><td>FAIL</td><td>fail</td><td>일을 잘못하여 뜻한 대로 되지 아니하거나 그르침</td></tr><tr><td>229</td><td>FAQ</td><td>FAQ</td><td>FREQUENTLY ASKED QUESTIONS</td><td>자주 묻는 질문들</td></tr><tr><td>230</td><td>팩스</td><td>FAX</td><td>fax</td><td>문자, 도표, 사진 따위의 정지 화면을 화소(畫素)로 분해하여 전기 신호로 바꾸어 전송하고, 수신 지점에서 원화(原畫)와 같은 수신 기록을 얻는 통신 방법. 또는 그런 기계 장치.</td></tr><tr><td>231</td><td>예정</td><td>FCST</td><td></td><td>앞으로 일어날 일이나 해야 할 일을 미리 정하거나 생각함.</td></tr><tr><td>232</td><td>파일</td><td>FILE</td><td>file</td><td>서류문서, 전자문서</td></tr><tr><td>233</td><td>핏</td><td>FIT</td><td>FIT</td><td>적합한, 맞는</td></tr><tr><td>234</td><td>고정</td><td>FIX</td><td>fixing</td><td>정한 대로 변경하지 않음</td></tr><tr><td>235</td><td>정액</td><td>FIXAMT</td><td>a fixed amount</td><td>일정하게 정하여진 액수</td></tr><tr><td>236</td><td>완료</td><td>FNSH</td><td>finish</td><td>완전히 끝마침</td></tr><tr><td>237</td><td>완료자</td><td>FNSHMN</td><td>finish man</td><td>완료작업을 수행한 자</td></tr><tr><td>238</td><td>푸터</td><td>FOTR</td><td>FOOTER</td><td>꼬리말(인쇄 때 문서의 각 페이지 아랫부분에 자동으로 첨가되는 표제, 날짜 등</td></tr><tr><td>239</td><td>팔로잉</td><td>FOWG</td><td>FOLLOWING</td><td>본인이 추가한 친구를 팔로잉이라고 한다.</td></tr><tr><td>240</td><td>팔로워</td><td>FOWR</td><td>FOLLOWER</td><td>소통망 서비스에서 특정한 사람이나 업체 따위의 계정을 즐겨 찾고 따르는 사람을 이르는 말.</td></tr><tr><td>241</td><td>현관</td><td>FRDR</td><td>FRONT DOOR</td><td>양식 건물의 주된 출입구에 나 있는 문간</td></tr><tr><td>242</td><td>해외</td><td>FRGN</td><td>foreign</td><td>바다의 밖</td></tr><tr><td>243</td><td>최초</td><td>FRST</td><td>first</td><td>맨 처음</td></tr><tr><td>244</td><td>첫</td><td>FST</td><td>first</td><td>맨 처음의</td></tr><tr><td>245</td><td>혜택</td><td>FVR</td><td>FAVOR</td><td>은혜와 덕택을 아울러 이르는 말</td></tr><tr><td>246</td><td>고정글</td><td>FXDC</td><td>fixing dictionary</td><td>고정되어 있는 글</td></tr><tr><td>247</td><td>정율</td><td>FXRT</td><td>a fixed rate</td><td>프로모션 적용시 정해진 비율로 적용을 할 경우사용함.</td></tr><tr><td>248</td><td>구분</td><td>GB</td><td>GBN</td><td>일정한 기준에 따라 전체를 몇 개로 갈라 나눔</td></tr><tr><td>249</td><td>안내</td><td>GD</td><td>GUIDE</td><td>어떤 내용을 소개하여 알려 줌. 또는 그런 일. 사정을 잘 모르는 어떤 사람을 가고자 하는 곳까지 데려다 주거나 그에게 여러 가지 사정을 알려 줌</td></tr><tr><td>250</td><td>생성</td><td>GEN</td><td>generation</td><td>새로 만듦</td></tr><tr><td>251</td><td>사은품</td><td>GFT</td><td>gift</td><td>받은 은혜에 사례하기 위한 물건</td></tr><tr><td>252</td><td>상품</td><td>GOODS</td><td>GOODS</td><td>쇼핑몰에서 판매하는 유형무형을 포함한 모든 상품</td></tr><tr><td>253</td><td>등급</td><td>GRADE</td><td>GRADE</td><td>여러 층으로 구분한 단계를 세는 단위</td></tr><tr><td>254</td><td>그룹</td><td>GRP</td><td>GROUP</td><td>함께 행동하거나 공통점이 있어 한데 묶일 수 있는 사람들의 무리.</td></tr><tr><td>255</td><td>수집</td><td>GTHR</td><td>GATHER</td><td>어떤 물건이나 자료들을 찾아서 모음</td></tr><tr><td>256</td><td>택배사</td><td>HDC</td><td>home-delivery company</td><td>우편물이나 짐, 상품 따위를 요구하는 장소까지 직접 배달해 주는 일을 하는 회사</td></tr><tr><td>257</td><td>도움말</td><td>HELP</td><td>HELP</td><td>많은 응용 프로그램에서 제공되는 도움 형식의 하나로, 응용 프로그램의 기능(특징) 사용에 관한 설명이나 지시 사항을 디스크에 기억시켜 놓은 것</td></tr><tr><td>258</td><td>밑단너비</td><td>HEM</td><td>HEM</td><td>밑단의 너비</td></tr><tr><td>259</td><td>헤더</td><td>HER</td><td>HEADER</td><td>아이덴티티인 블루 칼라를 포함하고 있으며 사이트 전체 최상단(로고아래) 공통으로 존재하는 영역.</td></tr><tr><td>260</td><td>높이</td><td>HIGH</td><td>height</td><td>높은 정도</td></tr><tr><td>261</td><td>엉덩이</td><td>HIP</td><td>HIP</td><td>볼기의 윗부분과 아랫부분을 통틀어 이르는 말</td></tr><tr><td>262</td><td>이력</td><td>HIST</td><td>history</td><td>지금까지 거쳐 온 학업, 직업, 경험 등의 내력</td></tr><tr><td>263</td><td>휴일</td><td>HOLI</td><td>HOLIDAY</td><td>나 공휴일 따위의 일을 하지 아니하고 쉬는 날</td></tr><tr><td>264</td><td>원산지</td><td>HOME</td><td>HOME</td><td>물건의 생산지를 이르는 말</td></tr><tr><td>265</td><td>HPOINT</td><td>HPOINT</td><td>HYUNDAI POINT</td><td>현대백화점그룹 통합멤버십</td></tr><tr><td>266</td><td>HS</td><td>HS</td><td>Harmonized Commodity Description and Coding System</td><td>국제통일상품분류체계(Harmonized Commodity Description and Coding System)의 약칭이다</td></tr><tr><td>267</td><td>HTML</td><td>HTML</td><td>html</td><td>hiper Text MarkUp Language</td></tr><tr><td>268</td><td>지수</td><td>IDX</td><td>INDEX</td><td>물가·노임 등 시기에 따른 변동을 기준한 때를 100으로 하여 비교하는 숫자</td></tr><tr><td>269</td><td>IE</td><td>IE</td><td>INTERNET EXPLORER</td><td>마이크로소프트에서 개발한 웹 브라우저이다</td></tr><tr><td>270</td><td>이미지</td><td>IMG</td><td>image</td><td>어떤 사람이나 사물로부터 받는 느낌, 상(像), 조상(彫像), 화상</td></tr><tr><td>271</td><td>불가</td><td>IMPS</td><td>IMPOSSIBILITY</td><td>할 수 없음. 되지 않음</td></tr><tr><td>272</td><td>포함</td><td>INCL</td><td>include</td><td>어떤 사물이나 현상 가운데 함께 들어 있거나 함께 넣음</td></tr><tr><td>273</td><td>수입자</td><td>INCOM</td><td>income</td><td>돈이나 물품 따위를 거두어들임, 개인, 국가, 단체 따위가 합법적으로 얻어 들이는 일정액의 금액</td></tr><tr><td>274</td><td>개인</td><td>IND</td><td>individual</td><td>국가나 사회, 단체 등을 구성하는 낱낱의 사람</td></tr><tr><td>275</td><td>지시</td><td>INDI</td><td>indication</td><td>일러서 시킴</td></tr><tr><td>276</td><td>개별</td><td>INDIV</td><td>individual</td><td>여럿 중 하나하나 또는 따로따로</td></tr><tr><td>277</td><td>정보</td><td>INFO</td><td>information</td><td>관찰이나 측정을 통하여 수집한 자료를 실제 문제에 도움이 될 수 있도록 정리한 지식</td></tr><tr><td>278</td><td>초기화</td><td>INI</td><td>initialize</td><td>프로그램의 실행을 위해 데이터 항목에 초기값을 넣는 것</td></tr><tr><td>279</td><td>문의</td><td>INQ</td><td>inquire</td><td>물어서 의논함</td></tr><tr><td>280</td><td>인심</td><td>INSM</td><td>INSEAM</td><td>안쪽 솔기 또는 안쪽 길이</td></tr><tr><td>281</td><td>입력</td><td>INSRT</td><td>insert</td><td>문자나 숫자를 컴퓨터가 기억하게 하는 일</td></tr><tr><td>282</td><td>관심</td><td>INTRS</td><td>INTEREST</td><td>어떤 것에 마음이 끌려 주의를 기울임. 또는 그런 마음이나 주의</td></tr><tr><td>283</td><td>할부</td><td>INST</td><td>monthly installment plan</td><td>돈을 여러 번에 나누어 냄.</td></tr><tr><td>284</td><td>결품</td><td>INSUFF</td><td>INSUFFICIENCY</td><td>여러 사유로 인해 정해진 수량에서 부족하거나 빠진 상품.</td></tr><tr><td>285</td><td>소득세</td><td>INTX</td><td>income tax</td><td>한해 수입에 대하여 액수별 기준에 따라 메기는 세금</td></tr><tr><td>286</td><td>운송장</td><td>INV</td><td>INVOICE</td><td>물건을 운반하는 증표</td></tr><tr><td>287</td><td>IP</td><td>IP</td><td>information provider</td><td>OSI 기본 참조 모델에서 제3계층인 망 계층에 해당하는 프로토콜 TCP/IP의 일부로 사용된다</td></tr><tr><td>288</td><td>발급사</td><td>ISCM</td><td>ISSUE COMPANY</td><td>발급을 해주는 회사</td></tr><tr><td>289</td><td>발급</td><td>ISSU</td><td>issue</td><td>증명서 따위를 발행하여 줌</td></tr><tr><td>290</td><td>발행</td><td>ISU</td><td>ISSUE</td><td>1.출판물이나 인쇄물을 찍어서 세상에 펴냄. 화폐, 증권, 증명서 따위를 만들어 세상에 내놓아 널리 쓰도록 함.</td></tr><tr><td>291</td><td>내선</td><td>ITEL</td><td>indoor telephone</td><td>내부의 선. 관청이나 회사 따위의 구내에서만 통하는 전화선.</td></tr><tr><td>292</td><td>항목</td><td>ITEM</td><td>item</td><td>하나의 일을 구성하고 있는 낱낱의 부분이나 갈래.</td></tr><tr><td>293</td><td>단품</td><td>ITM</td><td>item</td><td>단품이란 경의료(輕衣料)로 바지, 블라우스, 셔츠, 스커트 등과 같이 다른 것과 조합하여 입어야 하는 의복을 말한다</td></tr><tr><td>294</td><td>업무</td><td>JOB</td><td>JOB</td><td>직장 같은 곳에서 맡아서 하는 일</td></tr><tr><td>295</td><td>가입</td><td>JOIN</td><td>join</td><td>고객이 당사의 서비스를 사용하기 위해 신청하는 행위</td></tr><tr><td>296</td><td>보관</td><td>KEEP</td><td>KEEP</td><td>상품기술서에서 상품 설명시 사용되는 단어 물건을 맡아서 간직하고 관리함</td></tr><tr><td>297</td><td>키</td><td>KEY</td><td>key</td><td>매핑시 사용되는 항목</td></tr><tr><td>298</td><td>종류</td><td>KIND</td><td>kind</td><td>사물의 부문을 나누는 갈래</td></tr><tr><td>299</td><td>키워드</td><td>KWD</td><td>key word</td><td>데이터를 검색할 때에, 특정한 내용이 들어 있는 정보를 찾기 위하여 사용하는 단어나 기호</td></tr><tr><td>300</td><td>언어</td><td>LANG</td><td>LANGUAGE</td><td>생각, 느낌 따위를 나타내거나 전달하는 데에 쓰는 음성, 문자 따위의 수단. 또는 그 음성이나 문자 따위의 사회 관습적인 체계.</td></tr><tr><td>301</td><td>위도</td><td>LATT</td><td>LATITUDE</td><td>지도상의 가로선을 말한다</td></tr><tr><td>302</td><td>로컬</td><td>LCL</td><td>LOCAL</td><td>(현재 얘기되고 있거나 자신이 살고 있는 특정) 지역, 현지</td></tr><tr><td>303</td><td>세탁</td><td>LDRY</td><td>Laundry</td><td>주로 기계를 이용하여 더러운 옷이나 피륙 따위를 빠는 일. 자금, 경력 따위를 필요에 따라 여러 가지 방법으로 탈바꿈하는 일</td></tr><tr><td>304</td><td>최하위</td><td>LEAF</td><td>lowest rank</td><td>제일 밑</td></tr><tr><td>305</td><td>길이</td><td>LENG</td><td>length</td><td>긴정도를 표시하는 단위</td></tr><tr><td>306</td><td>문자</td><td>LET</td><td>Letter</td><td>말이나 소리를 눈으로 볼 수 있도록 적기 위한 일정한 체제의 부호</td></tr><tr><td>307</td><td>기간계</td><td>LGC</td><td>LEGACY</td><td>재고, 생산, 재무회계, 공급망, 구매 등과 같이 기업 경영의 '기간'이 되는 부분</td></tr><tr><td>308</td><td>대분류</td><td>LGRP</td><td>big class</td><td>크게 나누어 분류하는 것</td></tr><tr><td>309</td><td>물류</td><td>LGST</td><td>LOGISTICS</td><td>물적유통(Physical Distribution)을 줄인 말로 생산자로부터 소비자까지의 물의 흐름</td></tr><tr><td>310</td><td>좋아요</td><td>LIKE</td><td>LIKE</td><td>소셜 네트워크 서비스, 인터넷 포럼, 뉴스 웹사이트, 블로그와 같은 통신 소프트웨어의 한 기능으로, 여기에서 사용자는 특정한 콘텐츠를 좋아하거나 즐기거나 지지한다고 표현</td></tr><tr><td>311</td><td>한도</td><td>LIM</td><td>limit</td><td>일정한 정도. 또는 한정된 정도</td></tr><tr><td>312</td><td>한정</td><td>LIMT</td><td>limitation</td><td>수량이나 범위 따위를 제한하여 정함. 또는 그런 한도</td></tr><tr><td>313</td><td>라인</td><td>LINE</td><td>line</td><td>생산라인</td></tr><tr><td>314</td><td>연결</td><td>LINK</td><td>link</td><td>사물과 사물 또는 현상과 현상이 서로 이어지거나 관계를 맺음</td></tr><tr><td>315</td><td>연동</td><td>LINK</td><td>LINKAGE</td><td>한 부분을 움직이면 그와 연결된 다른 부분도 함께 움직이는 일</td></tr><tr><td>316</td><td>리스트</td><td>LIST</td><td>LIST</td><td>물품이나 사람의 이름 따위를 일정한 순서로 적어 놓은 것.</td></tr><tr><td>317</td><td>제한</td><td>LMT</td><td>limit</td><td>일정한 한도를 정하거나 그 한도를 넘지 못하게 막음</td></tr><tr><td>318</td><td>경도</td><td>LNGT</td><td>LONGITUDE</td><td>그리니치를 본초자오선으로 하여 그 서쪽과 동쪽의 위치를 측정하는 것입니다</td></tr><tr><td>319</td><td>잠김</td><td>LOCK</td><td>LOCK</td><td>자물쇠가 채워짐. 물이나 가스 등이 흘러 나오지 못하게 차단. 가라 앉음</td></tr><tr><td>320</td><td>로그</td><td>LOG</td><td>log</td><td>작업흔적</td></tr><tr><td>321</td><td>로그인</td><td>LOGIN</td><td>log in</td><td>단말기를 이용하여 멀리 떨어져 있는 컴퓨터의 운영체계나 응용 프로그램을 이용하기 위하여 필요한 절차</td></tr><tr><td>322</td><td>추첨</td><td>LOT</td><td>drawing lots</td><td>제비를 뽑음</td></tr><tr><td>323</td><td>로그아웃</td><td>LOUT</td><td>log out</td><td>단말기와 통신 회선의 데이터 송수신이 종료되어 단말기가 개방 상태로 있게 되는 것.</td></tr><tr><td>324</td><td>하위</td><td>LOWR</td><td>lower</td><td>낮은 위치나 지위</td></tr><tr><td>325</td><td>대</td><td>LRG</td><td>large</td><td>큰것</td></tr><tr><td>326</td><td>최종</td><td>LST</td><td>LAST</td><td>맨 나중</td></tr><tr><td>327</td><td>레벨</td><td>LVL</td><td>LEVEL</td><td>지위나 품질 따위의 일정한 표준이나 정도</td></tr><tr><td>328</td><td>메인</td><td>MAIN</td><td>main</td><td>가장 중요하거나 주된 것</td></tr><tr><td>329</td><td>남성</td><td>MALE</td><td>male</td><td>성(性)의 측면에서 남자를 이르는 말. 특히, 성년(成年)이 된 남자를 이른다.</td></tr><tr><td>330</td><td>몰</td><td>MALL</td><td>mall</td><td>충분한 주차장을 갖춘 보행자 전용 상점가( = 쇼핑몰 )</td></tr><tr><td>331</td><td>수기</td><td>MANUAL</td><td>MANUAL</td><td>글이나 글씨를 자기 손으로 직접 씀</td></tr><tr><td>332</td><td>제조</td><td>MANUF</td><td>making;manufacture;production;construction</td><td>공장에서 큰 규모로 물건을 만듦</td></tr><tr><td>333</td><td>매핑</td><td>MAPP</td><td>mapping</td><td>매핑</td></tr><tr><td>334</td><td>소재</td><td>MAT</td><td>MATERIAL</td><td>어떤 것을 만드는 데 바탕이 되는 재료</td></tr><tr><td>335</td><td>최대</td><td>MAX</td><td>max</td><td>수나 양, 정도 따위가 가장 큼</td></tr><tr><td>336</td><td>회원</td><td>MBR</td><td>membership</td><td>단체를 구성하는 일원(一員). 회원</td></tr><tr><td>337</td><td>멤버쉽</td><td>MBSP</td><td>membership</td><td>구성원으로서의 자격이나 지위</td></tr><tr><td>338</td><td>MD</td><td>MD</td><td>merchandise</td><td>1 매매[거래]하다 2 판매를 계획·촉진하다; 광고 선전하다</td></tr><tr><td>339</td><td>필수</td><td>MDTY</td><td>mandatory</td><td>꼭 있어야 하거나 하여야 함, 반드시 있어야 함. 또는 반드시 쓰임</td></tr><tr><td>340</td><td>둘레</td><td>MEAMT</td><td>MEASUREMENT</td><td>사물의 테두리나 바깥 언저리. 측정,측량, (무엇의)치수</td></tr><tr><td>341</td><td>수단</td><td>WAY</td><td>WAY</td><td>방법의 의미</td></tr><tr><td>342</td><td>매체</td><td>MEDIA</td><td>media</td><td>정보공개 실시에 사용되는 물체</td></tr><tr><td>343</td><td>미디어</td><td>MSMDA</td><td>mass media</td><td>어떤 작용을 한쪽에서 다른 쪽으로 전달하는 역할을 하는 것.</td></tr><tr><td>344</td><td>메모</td><td>MEMO</td><td>memo</td><td>다른 사람에게 말을 전하거나 자신의 기억을 돕기 위하여 짤막하게 글로 남김</td></tr><tr><td>345</td><td>메뉴</td><td>MENU</td><td>menu</td><td>컴퓨터가 운용자에게 제시하는 선택목록</td></tr><tr><td>346</td><td>가맹점</td><td>MERS</td><td>credit card member store</td><td>가맹점은 신용카드가맹점을 의미한다 즉 신용카드(체크카드,기프트카드 포함) 결제를 위해 약정된 가맹점을 말한다. 약정은 가맹점 코드(가맹점번호) 단위로 하며, 목적에 따른 가맹점 코드를 개설해야하므로 여러 가맹점 코드가 생성될 수 있다.</td></tr><tr><td>347</td><td>방식</td><td>METH</td><td>method</td><td>일정한 방법이나 형식</td></tr><tr><td>348</td><td>방법</td><td>METHOD</td><td>METHOD</td><td>어떤 일을 해 나가거나 목적을 이루기 위하여 취하는 수단이나 방식</td></tr><tr><td>349</td><td>메소드</td><td>MTHOD</td><td>METHOD</td><td>서버에서 어떤 작동을 구현하고 요청 서비스를 수행할 수 있도록 만들어진 단일 요구 메시지</td></tr><tr><td>350</td><td>제조사</td><td>MFCO</td><td>a manufacturing company</td><td>제조 회사(製造會社) a manufacturing company</td></tr><tr><td>351</td><td>관리</td><td>MGR</td><td>MANAGEMENT</td><td>어떤 일을 맡아 관할 처리함</td></tr><tr><td>352</td><td>중분류</td><td>MGRP</td><td>middle group</td><td>종류에 따라서 가름는 중간</td></tr><tr><td>353</td><td>중</td><td>MID</td><td>MIDDLE</td><td>여럿의 가운데.</td></tr><tr><td>354</td><td>마일리지</td><td>MILG</td><td>mileage</td><td>정 고객 확보를 위한 기업의 판매 촉진 프로그램</td></tr><tr><td>355</td><td>최소</td><td>MIN</td><td>minimum</td><td>수나 정도 따위가 가장 작음</td></tr><tr><td>356</td><td>기획전</td><td>MKDP</td><td>make a plan to display</td><td>일정한 목적을 위하여 또는 특정의 주제를 담아 기획된 전시회나 전람회</td></tr><tr><td>357</td><td>기획</td><td>MKPL</td><td>make a plan</td><td>일을 꾀하여 계획함</td></tr><tr><td>358</td><td>이용</td><td>MKUS</td><td>make a use</td><td>대상을 필요에 따라 이롭게 씀</td></tr><tr><td>359</td><td>다국어</td><td>MNY_LANG</td><td>MANY LANGUAGES</td><td>여러 나라의 말.</td></tr><tr><td>360</td><td>모바일</td><td>MOBL</td><td>mobile</td><td>정보 통신에서 이동성을 가진 것을 통틀어 이르는 말.</td></tr><tr><td>361</td><td>수정</td><td>MOD</td><td>modify</td><td>잘못된 점 등을 정리하고 고침</td></tr><tr><td>362</td><td>제품</td><td>MODEL</td><td>model</td><td>원료를 써서 물건을 만듦. 또는 그렇게 만들어 낸 물품</td></tr><tr><td>363</td><td>모델</td><td>MODL</td><td>model</td><td>작품을 만들기 전에 미리 만든 물건. 또는 완성된 작품의 대표적인 보기. 본보기가 되는 대상이나 모범.</td></tr><tr><td>364</td><td>개월</td><td>MON</td><td>months</td><td>월수를 세는 단위</td></tr><tr><td>365</td><td>이동</td><td>MOV</td><td>movement</td><td>움직여 옮김. 또는 움직여 자리를 바꿈</td></tr><tr><td>366</td><td>마진</td><td>MRGN</td><td>gross profit</td><td>저항값을 측정후 남은 마진(이익)</td></tr><tr><td>367</td><td>표기</td><td>NTTN</td><td>NOTATION</td><td>적어서 나타냄. 또는 그런 기록. 문자 또는 음성 기호로 언어를 표시함</td></tr><tr><td>368</td><td>마크</td><td>MRK</td><td>MARK</td><td>어떠한 뜻을 나타내기 위해 쓰는 부호나 문자</td></tr><tr><td>369</td><td>메시지</td><td>MSG</td><td>message</td><td>언어나 기호에 의하여 전달되는 정보 내용</td></tr><tr><td>370</td><td>쪽지</td><td>NOTE</td><td>NOTE</td><td>어떤 내용의 글을 적은 종이쪽.</td></tr><tr><td>371</td><td>사항</td><td>MTR</td><td>MATTER</td><td>일의 조목</td></tr><tr><td>372</td><td>이관</td><td>MVOT</td><td>TRANSFER OF CONTROL</td><td>관할을 옮김. 또는 옮기어 관할함</td></tr><tr><td>373</td><td>혼용률</td><td>MXRT</td><td>Mixed usage rate</td><td>혼방이나 교직으로 짠 직물에서, 직물을 구성하는 섬유의 비율</td></tr><tr><td>374</td><td>본인</td><td>MY</td><td>the person in question;the person himself[herself]</td><td>공식적인 자리에서 ‘나’를 문어적으로 이르는 말.(자기, 자신)</td></tr><tr><td>375</td><td>내외국인</td><td>NAFR</td><td>native or foreigners</td><td>내국인 및 외국인</td></tr><tr><td>376</td><td>성명</td><td>NAME</td><td>name</td><td>성과 이름을 아울러 이르는 말.</td></tr><tr><td>377</td><td>개수</td><td>NCNT</td><td>count</td><td>한 개씩 낱으로 셀 수 있는 물건의 수효.</td></tr><tr><td>378</td><td>필요</td><td>NEED</td><td>need</td><td>꼭 요구되는 바가 있음</td></tr><tr><td>379</td><td>무이자</td><td>NINT</td><td>no interest</td><td>이자가 붙지 않음</td></tr><tr><td>380</td><td>닉네임</td><td>NKNM</td><td>NICKNAME</td><td>‘별명’, ‘애칭’으로 순화.</td></tr><tr><td>381</td><td>명</td><td>NM</td><td>name</td><td>‘이름’의 뜻을 나타내는 말.</td></tr><tr><td>382</td><td>번호</td><td>NO</td><td>number</td><td>차례를 나타내거나 식별하기 위해 붙이는 숫자</td></tr><tr><td>383</td><td>정상</td><td>NOR</td><td>NORMAL</td><td>특별한 변동이나 탈이 없이 제대로인 상태</td></tr><tr><td>384</td><td>알림</td><td>NOTI</td><td>notify; inform</td><td>어떤 내용을 소개하여 알려 줌. 또는 그런 일</td></tr><tr><td>385</td><td>고시</td><td>ANNC</td><td>ANNOUNCEMENT</td><td>어떤 내용을 소개하여 알려 줌. 또는 그런 일</td></tr><tr><td>386</td><td>NOW</td><td>NOW</td><td>NOW</td><td>지금</td></tr><tr><td>387</td><td>면세</td><td>NTAX</td><td>NOT TAX</td><td>세금을 면제함. 면세제도는 조세(租稅)의 전부에 대한 납부의무를 면제하는 것으로서 조세의 일부에 대한 납부의무를 면제하는 감세제도(減稅制度)와 더불어 조세감면제도.</td></tr><tr><td>388</td><td>공지</td><td>NTC</td><td>Notice</td><td>세상에 널리 알림.</td></tr><tr><td>389</td><td>고지</td><td>NTFC</td><td>NOTIFICATION</td><td>[告知] 게시나 글을 통하여 알림</td></tr><tr><td>390</td><td>미출고</td><td>NTKW</td><td>not taking goods out of the warehouse</td><td>출고되지 않음</td></tr><tr><td>391</td><td>OB</td><td>OB</td><td>outbound</td><td>outbound 마케팅</td></tr><tr><td>392</td><td>목적</td><td>PRPOS</td><td>PURPOSE</td><td>실현하려고 하는 일이나 나아가는 방향</td></tr><tr><td>393</td><td>객체</td><td>OBJ</td><td>OBJECT</td><td>작용의 대상이 되는 것</td></tr><tr><td>394</td><td>직책</td><td>OCP</td><td>occupy</td><td>직무상의 책임</td></tr><tr><td>395</td><td>발생</td><td>OCUR</td><td>occurance</td><td>어떤 일이나 사물이 생겨남</td></tr><tr><td>396</td><td>오에라</td><td>OERA</td><td>OERA</td><td>한섬의 업체배송사</td></tr><tr><td>397</td><td>온라인</td><td>ONL</td><td>on line</td><td>[명사] 통신 회선 따위를 이용하여 정보를 보낼 수 있는 상태. [명사] 컴퓨터 시스템에서 주변·외부 장치가 중앙 처리 장치(CPU)의 직접 제어하에 있는 상태.</td></tr><tr><td>398</td><td>온오프</td><td>ONOFF</td><td>on off line</td><td>온라인 오프라인</td></tr><tr><td>399</td><td>주문</td><td>ORD</td><td>order</td><td>어떤 상품을 만들거나 파는 사람에게 그 상품의 생산이나 수송, 또는 서비스의 제공을 요구하거나 청구함. 또는 그 요구나 청구</td></tr><tr><td>400</td><td>주문자</td><td>ORDMN</td><td>AN ORDER MAN</td><td>물품 따위를 주문하는 사람이나 단체</td></tr><tr><td>401</td><td>고유</td><td>ORG</td><td>ORIGINAL</td><td>본디부터 지니고 있거나 어느 사물에만 특별히 있는 것</td></tr><tr><td>402</td><td>원본</td><td>ORGNL</td><td>original</td><td>근본이 되는 서류나 문건 따위</td></tr><tr><td>403</td><td>기관</td><td>ORN</td><td>organization</td><td>법인이나 그 밖의 단체의 의사 결정 또는 실행에 참여하는 지위에 있고 그 행위가 법인 행위로 간주되는 개인이나 집단</td></tr><tr><td>404</td><td>당사</td><td>OUR</td><td>OUR COMPANY</td><td>이 회사 , 우리회사</td></tr><tr><td>405</td><td>자사</td><td>OURCOM</td><td>OUR COMPANY</td><td>자기가 소속하여 있는 회사</td></tr><tr><td>406</td><td>아울렛</td><td>OUTLET</td><td>OUTLET</td><td>재고품이나 이월 상품을 한곳에 모아 싸게 판매하는 곳 (표준어: 아웃렛 )</td></tr><tr><td>407</td><td>파트</td><td>PART</td><td>PART</td><td>전체를 구성하는 일부</td></tr><tr><td>408</td><td>경로</td><td>PATH</td><td>PATH</td><td>지나는 길. 일이 진행되는 방법이나 순서</td></tr><tr><td>409</td><td>결제</td><td>PAY</td><td>PAYMENT</td><td>대금을 주고받아 매매 당사자 사이의 거래 관계를 끝맺는 일</td></tr><tr><td>410</td><td>지급</td><td>PAYS</td><td>PAYMENTS</td><td>돈이나 물품 따위를 정하여진 몫만큼 내줌</td></tr><tr><td>411</td><td>PB</td><td>PB</td><td>private brand</td><td>제조 설비를 가지지 않은 유통 전문 업체가 개발한 상표로, 유통 전문 업체가 스스로 독자적인 상품을 기획하여 생산만 제조업체인 메이커에 의뢰하는 것. 이러한 PB는 '유통업자 주도형 상표'라고 할 수 있으며, 유통업자가 상표의 소유권과 판매책임을 모두 갖게 된다. 반면, 이와는 반대로 NB(National Brand)라는 것이 있다. 이는 원칙적으로 전국적인 규모로 판매되고 있는 제조업체 중심의 메이커 브랜드를 의미한다.</td></tr><tr><td>412</td><td>공개</td><td>PBL</td><td>opening to the public</td><td>어떤 사실이나 사물, 내용 따위를 여러 사람에게 널리 터놓음</td></tr><tr><td>413</td><td>PC</td><td>PC</td><td>PERSONAL COMPUTER</td><td>퍼스널 컴퓨터</td></tr><tr><td>414</td><td>원가</td><td>PCOST</td><td>the prime cost</td><td>상품의 제조, 판매, 배급 따위에 든 재화와 용역을 단위에 따라 계산한 가격</td></tr><tr><td>415</td><td>기한</td><td>PERD</td><td>period</td><td>유효일이나 만료일과 같이 미리 한정하여 놓은 시기</td></tr><tr><td>416</td><td>PG</td><td>PG</td><td>PAYMENT GATEWAY</td><td>전자지불 서비스, PAYMENT GATEWAY의 약자로서 전자상거래 시장의 핵심인 전자지불 서비스</td></tr><tr><td>417</td><td>촬영</td><td>PHOTO</td><td>photographing</td><td>사람, 사물, 풍경 따위를 사진이나 영화로 찍음</td></tr><tr><td>418</td><td>사진</td><td>PICTR</td><td>PICTURE</td><td>사진기로 물체의 화상(畵像)을 찍어 내는 기술, 또는 인화지에 나타낸 그 화상</td></tr><tr><td>419</td><td>픽업</td><td>PIKUP</td><td>pick up</td><td>여럿 가운데서 골라냄</td></tr><tr><td>420</td><td>묶음</td><td>PKG</td><td>PACKAGE</td><td>한데 모아서 묶어 놓은 덩이. 수량을 나타내는 말 뒤에 쓰여｝ 묶어 놓은 덩이를 세는 단위.</td></tr><tr><td>421</td><td>경품</td><td>PMGD</td><td>PREMIMUM</td><td>특정한 기간 동안 많은 상품을 팔고 손님의 호감을 얻기 위해, 일정한 액수 이상의 상품을 사는 손님에게 곁들여 주는 물품</td></tr><tr><td>422</td><td>제공</td><td>POF</td><td>a proffer</td><td>물건 또는 기타사물을 지급함</td></tr><tr><td>423</td><td>포인트</td><td>POINT</td><td>point</td><td>중요한 사항이나 핵심</td></tr><tr><td>424</td><td>정책</td><td>POLC</td><td></td><td>정치적 목적을 실현하기 위한 방책</td></tr><tr><td>425</td><td>팝업</td><td>POPUP</td><td>POP UP</td><td>메인 화면 이외의 별도 화면</td></tr><tr><td>426</td><td>포스트</td><td>POST</td><td>POST</td><td>통신 회사에서 제공하는 컴퓨터 기반 메시징 시스템이나 온라인 포럼에 메시지를 게시하는 일</td></tr><tr><td>427</td><td>시점</td><td>POTM</td><td>a point of[in] time</td><td>시간의 흐름 가운데 어느 한 순간</td></tr><tr><td>428</td><td>가</td><td>PRC</td><td>price</td><td>'값’의 뜻을 더하는 접미사.</td></tr><tr><td>429</td><td>가격</td><td>PRCE</td><td>price</td><td>물건이 지니고 있는 가치</td></tr><tr><td>430</td><td>현황</td><td>PRCOND</td><td>present condition</td><td>현재의 상황</td></tr><tr><td>431</td><td>과정</td><td>PRCS</td><td>process</td><td>사물의 진행·발전하는 경로</td></tr><tr><td>432</td><td>선입금</td><td>PRCT</td><td>pre receipt</td><td>미리 은행 따위에 예금하거나 빚을 갚기 위하여 돈을 들여놓는 일</td></tr><tr><td>433</td><td>예측</td><td>PRDN</td><td>prediction</td><td>미리 헤아려 짐작함</td></tr><tr><td>434</td><td>선환불</td><td>PRRF</td><td>PRE repay, refund</td><td>미리 돈이나 물건을 바꾸어 지불함</td></tr><tr><td>435</td><td>증정품</td><td>PREST</td><td>presentation</td><td>남에게 물건을 드림.</td></tr><tr><td>436</td><td>프로필</td><td>PRFLE</td><td>Profile</td><td>약력</td></tr><tr><td>437</td><td>진행</td><td>PRGS</td><td>progress</td><td>일 따위를 처리하여 나감</td></tr><tr><td>438</td><td>우선</td><td>PRIO</td><td>priority</td><td>딴 것에 앞서 특별하게 대우함</td></tr><tr><td>439</td><td>약속</td><td>PRMS</td><td>promis</td><td>다른 사람과 앞으로의 일을 어떻게 할 것인가를 미리 정하여 둠. 또는 그렇게 정한 내용</td></tr><tr><td>440</td><td>파라미터</td><td>PRMT</td><td>parameter</td><td>어떤 조건 아래서는 일정한 값을 갖고 있으나 조건을 변화시키 면 다른 값을 갖는 계수</td></tr><tr><td>441</td><td>처리</td><td>PROC</td><td>process</td><td>사무나 사건 따위를 절차에 따라 정리하여 치르거나 마무리를 지음</td></tr><tr><td>442</td><td>절차</td><td>PROE</td><td>procedure</td><td>일을 치르는 데 거쳐야 하는 순서나 방법.</td></tr><tr><td>443</td><td>프로모션</td><td>PROMO</td><td>promotion</td><td>여러 가지 방법을 써서 수요를 불러일으키고 자극하여 판매가 늘도록 유도하는 일.흥행사(興行師)</td></tr><tr><td>444</td><td>선물</td><td>PRST</td><td>present</td><td>남에게 어떤 물건 따위를 선사함. 또는 그 물건</td></tr><tr><td>445</td><td>보존</td><td>PRSV</td><td>PRESERVATION</td><td>잘 지니어 상하거나 없어지거나 하지 않도록 함</td></tr><tr><td>446</td><td>출력</td><td>PRT</td><td>PRINT</td><td>컴퓨터 따위의 기기(機器)나 장치가 입력을 받아 일을 하고 외부로 결과를 내는 일</td></tr><tr><td>447</td><td>가능</td><td>PSB</td><td>possibility</td><td>할 수 있거나 될 수 있음</td></tr><tr><td>448</td><td>푸쉬</td><td>PUSH</td><td>PUSH</td><td>외국 프로 레슬링에서, 양손으로 상대편을 미는 기술</td></tr><tr><td>449</td><td>비밀번호</td><td>PWD</td><td>password</td><td>시스템 사용을 위해서 당사자끼리만 알 수 있도록 꾸민 약속 기호</td></tr><tr><td>450</td><td>문항</td><td>QEST</td><td>question</td><td>문제의 항목.</td></tr><tr><td>451</td><td>조회</td><td>QRY</td><td>query</td><td>어떤 사람의 인적 사항을 관계되는 기관에 알아보는 일, 특정 정보를 찾다</td></tr><tr><td>452</td><td>할당자</td><td>QTMN</td><td>quota man</td><td>몫을 갈라 나눔. 또는 그 몫의 담당자</td></tr><tr><td>453</td><td>재질</td><td>QTMT</td><td>the quality of the material</td><td>재기(材器)와 성질을 아울러 이르는 말. 재료가 가지는 성질. 목재가 가지는 성질</td></tr><tr><td>454</td><td>퀵</td><td>QUCK</td><td>QUICK</td><td>주로 부피가 크지 않은 서류나 작은 물건을 빠른 시간 내에 목적지에 전달하는 서비스</td></tr><tr><td>455</td><td>질문</td><td>QUEST</td><td>question</td><td>모르거나 의심나는 점을 물음</td></tr><tr><td>456</td><td>할당</td><td>QUOT</td><td>quota</td><td>몫을 갈라 나눔. 또는 그 몫</td></tr><tr><td>457</td><td>분기</td><td>QUTR</td><td>quarter</td><td>일 년을 4등분 한 3개월씩의 기간</td></tr><tr><td>458</td><td>율</td><td>RATE</td><td>rate</td><td>다른 수나 양에 대한 어떤 수나 양의 비</td></tr><tr><td>459</td><td>최근</td><td>RCNT</td><td>recent</td><td>얼마 되지 않은 지나간 날.</td></tr><tr><td>460</td><td>수령</td><td>RCV</td><td>receipt</td><td>돈이나 물품을 받아들임</td></tr><tr><td>461</td><td>수신</td><td>RECV</td><td>receive</td><td>신호를 받음</td></tr><tr><td>462</td><td>수신자</td><td>RECVMN</td><td>receive man</td><td>신호를 받는 사람</td></tr><tr><td>463</td><td>수취인</td><td>RCVMN</td><td>receive man</td><td>서류나 물건을 받는 사람</td></tr><tr><td>464</td><td>재</td><td>RE</td><td>re-, again</td><td>하던 것을 되풀이해서 또</td></tr><tr><td>465</td><td>읽기</td><td>READ</td><td>Read</td><td>게시판에서 읽기 권한 여부 설정</td></tr><tr><td>466</td><td>기록</td><td>REC</td><td>RECORD</td><td>어떤 사실이나 내용을 필기도구로) 글자를 이루어 나타내는 것. 또는, 그 글</td></tr><tr><td>467</td><td>추천</td><td>RECOM</td><td>recommendation</td><td>어떤 조건에 적합한 대상을 책임지고 소개함</td></tr><tr><td>468</td><td>영수증</td><td>RECT</td><td>receipt</td><td>돈이나 물품 따위를 받은 사실을 표시하는 증서</td></tr><tr><td>469</td><td>참조</td><td>REF</td><td>REFERENCE</td><td>참고로 비교하고 대조하여 봄</td></tr><tr><td>470</td><td>참고</td><td>REFC</td><td>reference</td><td>살펴서 도움이 될 만한 재료로 삼음</td></tr><tr><td>471</td><td>반영</td><td>REFT</td><td>reflection</td><td>다른 것에 영향을 받아 어떤 현상이 나타남. 또는 어떤 현상을 나타냄</td></tr><tr><td>472</td><td>등록</td><td>REG</td><td>REGISTRATION</td><td>문서나 데이터를 기록하여 둠</td></tr><tr><td>473</td><td>지역</td><td>RGN</td><td>region</td><td>일정하게 구획된 어느 범위의 토지</td></tr><tr><td>474</td><td>지역별</td><td>REGNCL</td><td>classified by region</td><td>지역에 따라서 나눈 구별.</td></tr><tr><td>475</td><td>관계</td><td>REL</td><td>relation</td><td>둘 이상의 사람· 사물·현상 등이 서로 관련을 맺음</td></tr><tr><td>476</td><td>관련</td><td>RELI</td><td>relation</td><td>사물, 현상이 관계를 맺어 매여 있음</td></tr><tr><td>477</td><td>대표</td><td>REP</td><td>REPRESENTATION</td><td>어떤 단체나 법인의 기관이 어떤 행위를 하면 그 단체나 법인의 행위와 같은 법률 효과가 발생할 때, 그 기관을 이르는 말.</td></tr><tr><td>478</td><td>요청</td><td>REQ</td><td>request</td><td>필요한 일이 이루어지도록 요긴하게 부탁함</td></tr><tr><td>479</td><td>요청자</td><td>REQMN</td><td>request man</td><td>요청하는 사람</td></tr><tr><td>480</td><td>성과</td><td>RESL</td><td>result</td><td>이루어 낸 결실</td></tr><tr><td>481</td><td>응대</td><td>RESP</td><td>a response</td><td>부름이나 물음 또는 요구 따위에 응하여 상대함</td></tr><tr><td>482</td><td>리뷰</td><td>REV</td><td>review</td><td>전체를 대강 살펴보거나 중요한 내용이나 줄거리를 대강 추려 냄</td></tr><tr><td>483</td><td>환불</td><td>RFD</td><td>repay, refund</td><td>돈이나 물건을 바꾸어 지불함</td></tr><tr><td>484</td><td>RGB</td><td>RGB</td><td>RED GREEN BLUE</td><td>RGB(적·녹·청)에 의해 색을 정의하는 색 모델, 또는 색 표시 방식.</td></tr><tr><td>485</td><td>배점</td><td>RGE</td><td>Rating grade</td><td>점수를 각각 나누어 배정함. 또는 그렇게 하여 정해진 점수</td></tr><tr><td>486</td><td>비고</td><td>RMK</td><td>remark</td><td>문서 따위에서, 그 내용에 참고가 될 만한 사항을 보충하여 적는 것. 또는 그 사항</td></tr><tr><td>487</td><td>순위</td><td>RNK</td><td>rank</td><td>차례나 순서를 나타내는 위치나 지위.</td></tr><tr><td>488</td><td>룰렛</td><td>ROLT</td><td>roulette</td><td>도박 기구의 하나.</td></tr><tr><td>489</td><td>응답</td><td>RPLY</td><td>REPLY</td><td>부름이나 물음에 응하여 답함</td></tr><tr><td>490</td><td>대표자</td><td>RPSTMN</td><td>representation man</td><td>대표하는 사람</td></tr><tr><td>491</td><td>결과</td><td>RSLT</td><td>result</td><td>어떤 원인으로 결말이 생김. 또는 그 상태</td></tr><tr><td>492</td><td>귀책</td><td>RSPN</td><td>responsible</td><td>법상 넓은 의미로 결과를 원인에 결부시키는 판단을 이르는 말</td></tr><tr><td>493</td><td>책임</td><td>RSPS</td><td>responsibility</td><td>맡아서 해야 할 임무나 의무</td></tr><tr><td>494</td><td>적립</td><td>RSRV</td><td>reserve</td><td>모아서 쌓아 둠</td></tr><tr><td>495</td><td>예약</td><td>RSV</td><td>RESERVATION</td><td>미리 약속함. 또는 미리 정한 약속</td></tr><tr><td>496</td><td>권한</td><td>RT</td><td>right</td><td>어떤 사람이나 기관의 권리나 권력이 미치는 범위</td></tr><tr><td>497</td><td>실시간</td><td>RLTM</td><td>realtime</td><td>집계 데이터가 아닌 실시간 데이터를 조회하는 경우 사용</td></tr><tr><td>498</td><td>반송</td><td>RTRN</td><td>return</td><td>주소나 수취인이 불명확한 우편물의 되돌아오는 업무</td></tr><tr><td>499</td><td>반품</td><td>RTN</td><td>RETURN</td><td>장비를 되돌려 줌</td></tr><tr><td>500</td><td>반품비</td><td>RTNX</td><td>RETURN COST</td><td>반품 시에 소요되는 비용</td></tr><tr><td>501</td><td>리턴</td><td>RETRN</td><td>RETURN</td><td>되돌아가다</td></tr><tr><td>502</td><td>환입</td><td>PRTN</td><td>PURCHASE RETURN</td><td>바꾸어 넣음</td></tr><tr><td>503</td><td>해제</td><td>RVC</td><td>revocation</td><td>묶인 것이나 행동에 제약을 가하는 법령 따위를 풀어 자유롭게 함.</td></tr><tr><td>504</td><td>해제자</td><td>RVCMN</td><td>revocation man</td><td>해제하는 사람</td></tr><tr><td>505</td><td>안전</td><td>SAFE</td><td>safe</td><td>위험이 생기거나 사고가 날 염려가 없음. 또는 그런 상태</td></tr><tr><td>506</td><td>매출</td><td>SALES</td><td>putting on sale</td><td>물건을 내다 파는 일</td></tr><tr><td>507</td><td>판매</td><td>SALE</td><td>sale</td><td>상품 따위를 팖</td></tr><tr><td>508</td><td>판매자</td><td>SALEMN</td><td>SALE MAN</td><td>상품 따위를 파는 사람. 또는 그 기관.</td></tr><tr><td>509</td><td>응모</td><td>SBSC</td><td>SUBSCRIBE</td><td>모집에 응하거나 지원함.</td></tr><tr><td>510</td><td>차감</td><td>SBTC</td><td>SUBTRACTION</td><td>비교하여 덜어 냄. 또는 비교하여 줄어든 차이</td></tr><tr><td>511</td><td>검색</td><td>SCH</td><td>search</td><td>책이나 컴퓨터에서, 안에 들어 있는 자료 가운데 목적에 따라 필요한 자료들을 찾아내는 일</td></tr><tr><td>512</td><td>범위</td><td>SCOP</td><td>scope</td><td>테두리가 정하여진 구역</td></tr><tr><td>513</td><td>점수</td><td>SCR</td><td>SCORE</td><td>성적을 나타내는 숫자</td></tr><tr><td>514</td><td>화면</td><td>SCRN</td><td>screen</td><td>텔레비전이나 컴퓨터 따위에서 그림이나 영상이 나타나는 면</td></tr><tr><td>515</td><td>글</td><td>SCRP</td><td>script</td><td>어떤 생각이나 일 따위의 내용을 글자로 나타낸 기록</td></tr><tr><td>516</td><td>비밀</td><td>SCRT</td><td>Secret</td><td>숨기어 남에게 드러내거나 알리지 말아야 할 일. 밝혀지지 않았거나 알려지지 않은 내용.</td></tr><tr><td>517</td><td>토요일</td><td>SAT</td><td>Saturday</td><td>월요일을 기준으로 한 주의 여섯째 날</td></tr><tr><td>518</td><td>선택</td><td>SEL</td><td>select</td><td>여럿 가운데서 어떤 것을 뽑아 정함</td></tr><tr><td>519</td><td>순번</td><td>SEQ</td><td>sequence</td><td>연속적인 번호의 나열</td></tr><tr><td>520</td><td>순서</td><td>SNO</td><td>SERIAL NUMBER</td><td>정하여진 기준에서 말하는 전후, 좌우, 상하 따위의 차례 관계, 무슨 일을 행하거나 무슨 일이 이루어지는 차례</td></tr><tr><td>521</td><td>차수</td><td>ODR</td><td>ORDERS</td><td>차례의 횟수</td></tr><tr><td>522</td><td>세션</td><td>SESS</td><td>session</td><td>[IT용어] (1) 망 환경에서 사용자 간 또는 컴퓨터 간의 대화를 위한 논리적 연결. (2) 프로세스들 사이에 통신을 수행하기 위해서 메시지 교환을 통해 서로를 인식한 이후부터 통신을 마칠 때까지의 기간.</td></tr><tr><td>523</td><td>세트</td><td>SET</td><td>set</td><td>도구나 가구 따위의 한 벌. 영화, 텔레비전 드라마 따위의 촬영에 쓰기 위하여 꾸민 여러 장치</td></tr><tr><td>524</td><td>설정</td><td>SETUP</td><td>setup</td><td>새로 만들어 정해 둠</td></tr><tr><td>525</td><td>성별</td><td>SEX</td><td>sex</td><td>남녀의 구분</td></tr><tr><td>526</td><td>소분류</td><td>SGRP</td><td>small group</td><td>동일한 속성을 가진 것들에 대한 소집합</td></tr><tr><td>527</td><td>신청</td><td>SGT</td><td>SUBSCRIPTION</td><td>어떤 일을 해 주거나 어떤 물건을 내줄 것을 청구하는 일, 또는 청구하기 위해 의사 표시를 하는 일</td></tr><tr><td>528</td><td>신청자</td><td>SGTMN</td><td>SUBSCRIPTION MAN</td><td>신청을 한 사람</td></tr><tr><td>529</td><td>공유</td><td>SHAR</td><td>share</td><td>공동으로 소유함.</td></tr><tr><td>530</td><td>출고</td><td>SHIP</td><td>shipping</td><td>창고에서 물품을 꺼냄, 생산자가 생산품을 시장에 냄</td></tr><tr><td>531</td><td>매장</td><td>SHOP</td><td>SHOP</td><td>물건을 파는 장소</td></tr><tr><td>532</td><td>상점</td><td>STORE</td><td>STORE</td><td>일정한 시설을 갖추고 물건을 파는 곳</td></tr><tr><td>533</td><td>쇼핑몰</td><td>SHML</td><td>SHOPPING MALL</td><td>한군데에서 여러 가지 물건을 살 수 있도록 상점들이 모여 있는 곳.</td></tr><tr><td>534</td><td>단축</td><td>SHRT</td><td>short</td><td>시간이나 거리 따위가 짧게 줄어듦</td></tr><tr><td>535</td><td>사이트</td><td>SITE</td><td>site</td><td>인터넷에서 사용자들이 정보가 필요할 때 언제든지 그것을 볼 수 있도록 웹 서버에 저장된 집합체</td></tr><tr><td>536</td><td>상황</td><td>SITU</td><td>situation</td><td>일이 되어 가는 과정이나 형편</td></tr><tr><td>537</td><td>상황별</td><td>SITUCL</td><td>classified by situation</td><td>상황에 따른 구별.</td></tr><tr><td>538</td><td>크기</td><td>SIZE</td><td>size</td><td>사물의 넓이, 부피, 양 따위의 큰 정도.</td></tr><tr><td>539</td><td>기술서</td><td>SKDOC</td><td>skill document</td><td>과학 이론을 실제로 적용하여 자연의 사물을 인간 생활에 유용하도록 가공하는 수단을 기록한 문서</td></tr><tr><td>540</td><td>기술</td><td>SKIL</td><td>skill</td><td>과학 이론을 실제로 적용하여 자연의 사물을 인간 생활에 유용하도록 가공하는 수단</td></tr><tr><td>541</td><td>스킬</td><td>SKL</td><td>skill</td><td>종업원의 스킬</td></tr><tr><td>542</td><td>판매사</td><td>SLCO</td><td>a sale company</td><td>상품의 판매를 행하는 회사</td></tr><tr><td>543</td><td>세일전</td><td>SLDS</td><td>sale display</td><td>고정 세일상품의 전시 행사</td></tr><tr><td>544</td><td>소매부리</td><td>SLEV_BRK</td><td>SLEEVE BREAK</td><td>소매 끝부분 수구(손이 들어가는 입구) 부분의 명칭</td></tr><tr><td>545</td><td>소매길이</td><td>SLEV_LENG</td><td>SLEEVE LENGTH</td><td>소매(윗옷의)</td></tr><tr><td>546</td><td>전표</td><td>SLIP</td><td>SLIP</td><td>은행, 회사, 상점 따위에서 금전의 출납이나 거래 내용 따위를 간단히 적은 쪽지</td></tr><tr><td>547</td><td>선정</td><td>SLT</td><td>select</td><td>여럿 가운데서 어떤 것을 뽑아 정함</td></tr><tr><td>548</td><td>소</td><td>SML</td><td>smallness</td><td>규모나 크기에 따라 큰 것, 중간 것, 작은 것으로 구분하였을 때에 가장 작은 것을 이르는 말</td></tr><tr><td>549</td><td>SMS</td><td>SMS</td><td>short message service</td><td>휴대전화를 이용하는 사람들이 , 별도의 다른 장비를 사용하지 않고 휴대전화만으로도 짧은 문장의 메시지를 주고 받을 수 있는 서비스를 말한다. 단문메세지서비스라고도 한다.</td></tr><tr><td>550</td><td>일련번호</td><td>SN</td><td>SERIAL NUMBER</td><td>일률적으로 연속되어 있는 번호</td></tr><tr><td>551</td><td>시즌</td><td>SEASN</td><td>SEASON</td><td>어떤 활동이 활발히 이루어지는 시기. 또는 어떤 활동을 하기에 적절한 시기</td></tr><tr><td>552</td><td>발송</td><td>SND</td><td>send, dispatch</td><td>물건, 편지, 서류 따위를 우편이나 운송 수단을 이용하여 보냄</td></tr><tr><td>553</td><td>송신</td><td>TRSMN</td><td>TRANSMISSION</td><td>컴퓨터가 단말 장치 또는 다른 컴퓨터에 전송하기 위해 메시지를 회선에 보내는 과정.</td></tr><tr><td>554</td><td>발송자</td><td>SNDMN</td><td>send man</td><td>발송을 하는 자</td></tr><tr><td>555</td><td>SNS</td><td>SNS</td><td>Social Network Service</td><td>사용자 간의 자유로운 의사소통과 정보 공유, 그리고 인맥 확대 등을 통해 사회적 관계를 생성하고 강화해주는 온라인 플랫폼을 의미함</td></tr><tr><td>556</td><td>양력</td><td>SOLAR</td><td>SOLAR</td><td>지구가 태양의 둘레를 한 바퀴 도는 데 걸리는 시간을 1년으로 정한 역법</td></tr><tr><td>557</td><td>정렬</td><td>SORT</td><td>sort</td><td>가지런하게 줄지어 늘어섬</td></tr><tr><td>558</td><td>품절</td><td>SOUT</td><td>SOLDOUT</td><td>물건이 다 팔리고 없음. 판매가능 재고가 없음</td></tr><tr><td>559</td><td>쇼핑백</td><td>SPB</td><td>Shoppingbag</td><td>상품포장시 사용되는 쇼핑백</td></tr><tr><td>560</td><td>봉사료</td><td>SRVC</td><td>service charge</td><td>남을 위하여 일하거나 애쓴 수고로 받거나 주는 대가</td></tr><tr><td>561</td><td>임직원</td><td>STAF</td><td>staff</td><td>일정한 직장에 근무하는 사람을 통틀어 이르는 말</td></tr><tr><td>562</td><td>상태</td><td>STAT</td><td>STATUS</td><td>사물·현상이 처해 있는 형편이나 모양</td></tr><tr><td>563</td><td>기준</td><td>STD</td><td>STANDARD</td><td>기본이 되는 표준</td></tr><tr><td>564</td><td>표준</td><td>STDN</td><td>STANDARD</td><td>사물의 정도나 성격 따위를 알기 위한 근거나 기준.</td></tr><tr><td>565</td><td>스튜디오</td><td>STDO</td><td>studio</td><td>사진사, 미술가, 공예가, 음악가 등의 작업실. 영화 촬영소. 방송국에서, 방송 설비를 갖추고 방송을 하는 방.</td></tr><tr><td>566</td><td>단계</td><td>STEP</td><td>step</td><td>일이 진행되는 데 있어서, 그 차례나 수준에 따라 여럿으로 구분되는 각각의 과정</td></tr><tr><td>567</td><td>만족도</td><td>STFD</td><td>satisfaction degree</td><td>마음에 부족함이 없이 만족을 느끼는 정도</td></tr><tr><td>568</td><td>입고</td><td>WHSG</td><td>STAGE</td><td>물건을 창고에 넣음</td></tr><tr><td>569</td><td>재고</td><td>STK</td><td>stock</td><td>창고 따위에 쌓여 있음</td></tr><tr><td>570</td><td>스티커</td><td>STKR</td><td>STICKER</td><td>전 광고, 상표, 표지(標識) 따위로서 붙이는 종잇조각</td></tr><tr><td>571</td><td>중단</td><td>STOP</td><td>STOP</td><td>중도에서 끊어지거나 끊음</td></tr><tr><td>572</td><td>휴면</td><td>STP</td><td>STOP</td><td>협력사 결제계좌의 현재상태가 정상거래상태가 아닌 중지상태</td></tr><tr><td>573</td><td>시작</td><td>STR</td><td>START</td><td>어떤 일이나 행동의 처음 단계를 이룸. 또는 그 단계</td></tr><tr><td>574</td><td>스트링</td><td>STRNG</td><td>STRING</td><td>끈이나 줄 또는 문자열</td></tr><tr><td>575</td><td>스타일</td><td>STYLE</td><td>STYLE</td><td>복식이나 머리 따위의 모양. 일정한 방식</td></tr><tr><td>576</td><td>서브</td><td>SUB</td><td>sub</td><td>프로그램 가운데 하나 이상의 장소에서 필요할 때마다 되풀이해서 사용할 수 있는 부분적 프로그램</td></tr><tr><td>577</td><td>주체</td><td>SUBJ</td><td>the subject, the main body</td><td>주도적인 역할을 담당하는 사람(main body)</td></tr><tr><td>578</td><td>합계</td><td>SUM</td><td>summary</td><td>한데 합하여 계산함</td></tr><tr><td>579</td><td>요약</td><td>SUMR</td><td>summary</td><td>말이나 문장의 요점을 잡아서 간추림</td></tr><tr><td>580</td><td>공급</td><td>SUP</td><td>supply</td><td>수요에 응하여 물품을 제공함</td></tr><tr><td>581</td><td>지원</td><td>SUPP</td><td>support</td><td>지지하여 도움</td></tr><tr><td>582</td><td>유보</td><td>SUSP</td><td>suspend</td><td>일정한 권리나 의무 따위를 뒷날로 미루어 두거나 보존하는 일</td></tr><tr><td>583</td><td>서비스</td><td>SVC</td><td>service</td><td>고객 또는 이용자의 편익을 위한 노력, 기능 또는 사업</td></tr><tr><td>584</td><td>시스템</td><td>SYS</td><td>system</td><td>필요한 기능을 실현하기 위하여 관련 요소를 어떤 법칙에 따라 조합한 집합체</td></tr><tr><td>585</td><td>사이즈</td><td>SZ</td><td>SIZE</td><td>신발이나 옷의 치수</td></tr><tr><td>586</td><td>태그</td><td>TAG</td><td></td><td>글쓴이가 그 글을 검색하기 위하여 감성, 정황, 글쓴이 의지 따위를 나타내는 단어를 입력해 둔, 일종의 키워드들의 집합</td></tr><tr><td>587</td><td>토크</td><td>TALK</td><td>TALK</td><td>이야기, 대화, 논의</td></tr><tr><td>588</td><td>과면세</td><td>TAX</td><td>TAX</td><td>과세 면세의 줄임말</td></tr><tr><td>589</td><td>표</td><td>TB</td><td>a tabular statement</td><td>어떤 내용을 일정한 형식과 순서에 따라 보기 쉽게 나타낸 것</td></tr><tr><td>590</td><td>테이블</td><td>TBL</td><td>table</td><td>1.식탁, 탁자. 인자나 키에 의해서 식별될 수 있는 자료의 각 항목의 배열.</td></tr><tr><td>591</td><td>당일</td><td>TDAY</td><td>the day</td><td>일이 있는 바로 그날</td></tr><tr><td>592</td><td>전화</td><td>TEL</td><td>telephone</td><td>전화기를 이용하여 말을 주고받음. 말소리를 전파나 전류로 바꾸었다가 다시 말소리로 환원시켜 공간적으로 떨어져 있는 사람이 서로 이야기할 수 있게 만든 기계</td></tr><tr><td>593</td><td>기간</td><td>TERM</td><td>term</td><td>어느 시기부터 다른 어느 시기까지의 사이</td></tr><tr><td>594</td><td>터미널</td><td>TERML</td><td>terminal</td><td>항공, 열차, 버스 노선 따위의 맨 끝 지점. 또는 많은 교통 노선이 모여 있는 역</td></tr><tr><td>595</td><td>테스트</td><td>TEST</td><td>test</td><td>지식 수준이나 기술의 숙달 정도를 알아보는 절차</td></tr><tr><td>596</td><td>텍스트</td><td>TEXT</td><td>text</td><td>텍스트</td></tr><tr><td>597</td><td>대상</td><td>TGT</td><td>TARGET</td><td>어떤 일의 상대 또는 목표나 목적이 되는 것</td></tr><tr><td>598</td><td>목요일</td><td>THUR</td><td>Thursday</td><td>월요일을 기준으로 한 주의 넷째 날</td></tr><tr><td>599</td><td>편집샵</td><td>THEDITED</td><td>THE EDITED</td><td>한 매장에 2개 이상의 브랜드 제품을 모아 판매하는 유통 형태</td></tr><tr><td>600</td><td>허벅지</td><td>THIGH</td><td>THIGH</td><td>허벅다리 안쪽의 살이 깊은 곳. 넓적다리의 위쪽 부분.</td></tr><tr><td>601</td><td>세</td><td>THN</td><td>THIN</td><td>상세</td></tr><tr><td>602</td><td>썸네일</td><td>THNL</td><td>THUMBNAILS</td><td>작게 축소한 사진이나 그림을 가리킬 때 사용하는 것도 썸네일 이라고 한다</td></tr><tr><td>603</td><td>삼진아웃</td><td>THOC</td><td>tree out change</td><td>품질불만 평가에 대해 품질연구소에서 정한 원칙(xx건 출고 상품중 품질불만 목표대비 허용치를 초과한 상품들)에 대해 3회 이상 경고된 상품에 대해 판매중단을 한다.</td></tr><tr><td>604</td><td>제3자</td><td>THPR</td><td>THIRD PARTY</td><td>직접간련되지 않는 사람</td></tr><tr><td>605</td><td>트임길이</td><td>TIM_LENG</td><td>TIM LENGTH</td><td>옷 따위의 어느 한 부분을 틀때의 길이</td></tr><tr><td>606</td><td>제목</td><td>TITLE</td><td>title</td><td>작품이나 강연, 보고 따위에서, 그것을 대표하거나 내용을 보이기 위하여 붙이는 이름</td></tr><tr><td>607</td><td>토큰</td><td>TKN</td><td>TOKEN</td><td>일련의 문자열을 구분할 수 있는 단위이다</td></tr><tr><td>608</td><td>납기</td><td>TLMT</td><td>the time Limit</td><td>상품을 입고 시켜야 하는 시기나 기한</td></tr><tr><td>609</td><td>방영</td><td>TLV</td><td>televise</td><td>텔레비전으로 방송을 하는 일</td></tr><tr><td>610</td><td>시간</td><td>TM</td><td>TIME</td><td>어떤 시각에서 어떤 시각까지의 사이</td></tr><tr><td>611</td><td>타임</td><td>TIME</td><td>TIME</td><td>과거로부터 현재와 미래로 이어지는 무한한 것</td></tr><tr><td>612</td><td>회차</td><td>TMDT</td><td>TIMEDATE</td><td>횟수의 차례.</td></tr><tr><td>613</td><td>임시</td><td>TMP</td><td>temporary</td><td>미리 정하지 아니하고 그때그때 필요에 따라 정한 것</td></tr><tr><td>614</td><td>템플릿</td><td>TMPL</td><td>template</td><td>본뜨는 공구(工具), 형판(型板)</td></tr><tr><td>615</td><td>시간대</td><td>TMSL</td><td>a time slot</td><td>하루 중 어느 시각에서 어느 시각까지의 일정한 시간</td></tr><tr><td>616</td><td>총</td><td>TOT</td><td>total</td><td>모든 자료의 합</td></tr><tr><td>617</td><td>전달</td><td>TRAF</td><td>transfer</td><td>지시, 명령, 파라미터 따위를 전하여 이르게 함.</td></tr><tr><td>618</td><td>변환</td><td>TRANS</td><td>transformation</td><td>다르게 하여 바꿈. 또는 달라져서 바뀜.</td></tr><tr><td>619</td><td>추적</td><td>TRCE</td><td>trace</td><td>사물의 자취를 더듬어 감</td></tr><tr><td>620</td><td>거래</td><td>TRD</td><td>TRADE</td><td>주고받음. 또는 사고팖</td></tr><tr><td>621</td><td>대상자</td><td>TRGMN</td><td>target man</td><td>대상이 되는 사람이나 집단</td></tr><tr><td>622</td><td>대체</td><td>TRNF</td><td>transfer</td><td>다른 것으로 대신함</td></tr><tr><td>623</td><td>전송</td><td>TRNS</td><td>transmission</td><td>물건이나 편지 따위를 전하여 달라고 남에게 맡겨 보냄.</td></tr><tr><td>624</td><td>이체</td><td>TRSF</td><td>transfer</td><td>보내는 사람의 계좌에서 받는 사람의 당좌 예금이나 보통 예금 계좌에 바로 돈이 넘어가도록 하는 일</td></tr><tr><td>625</td><td>시도</td><td>TRY</td><td>try</td><td>어떤 것을 이루어 보려고 계획하거나 행동함.</td></tr><tr><td>626</td><td>타이틀</td><td>TTL</td><td>title</td><td>표제(表題) ·제명(題名) ·직함 ·칭호</td></tr><tr><td>627</td><td>제세공과금</td><td>TUBA</td><td>TAX UTILITY BILL AMT</td><td>취득을 위해 취득자가 부담하는 비용(세금), 경품의 경우 경품가액이 5만원이 초과되었을 時 고객이 부담하는 세금</td></tr><tr><td>628</td><td>유형</td><td>TYP</td><td>type</td><td>모양이나 형체</td></tr><tr><td>629</td><td>타입</td><td>TYPE</td><td>type</td><td>어떤 부류의 형식이나 형태.</td></tr><tr><td>630</td><td>비회원</td><td>UMEM</td><td>un-membership</td><td>어떤 조직이나 단체의 구성원이 아닌 사람.</td></tr><tr><td>631</td><td>단위</td><td>UNIT</td><td>unit</td><td>길이, 무게, 수효, 시간 따위의 수량을 수치로 나타낼 때 기초가 되는 일정한 기준</td></tr><tr><td>632</td><td>단가</td><td>UNPR</td><td>unit price</td><td>물건 한 단위(單位)의 가격</td></tr><tr><td>633</td><td>미납</td><td>UNSUPPLY</td><td>UNSUPPLY</td><td>내야 할 것을 아직 내지 않았거나 내지 못함</td></tr><tr><td>634</td><td>단가별</td><td>UPCL</td><td>classified by unit price</td><td>단위가격 별</td></tr><tr><td>635</td><td>상한</td><td>UPER</td><td>upper</td><td>위와 아래로 일정한 범위를 이루고 있을 때, 위쪽의 한계</td></tr><tr><td>636</td><td>업로드</td><td>UPLD</td><td>upload</td><td>한 컴퓨터 시스템에서 다른 시스템으로 파일을 전송하는 것</td></tr><tr><td>637</td><td>상단바</td><td>UPLN</td><td>Upper Line Area</td><td>TV, Monitor의 화면 바 위치입니다. BTLN의 정의를 참조하세요</td></tr><tr><td>638</td><td>상단</td><td>TTOP</td><td>THE TOP</td><td>화면의 상단을 의미하며 TV와 Monitor공용가능</td></tr><tr><td>639</td><td>상위</td><td>UPR</td><td>UPPER</td><td>조직 중에서 선두에 위치하는 부류</td></tr><tr><td>640</td><td>미처리</td><td>UPROC</td><td>UNPROCESS</td><td>아직 처리되지 않음</td></tr><tr><td>641</td><td>URL</td><td>URL</td><td>uniform resource locator</td><td>인터넷에 존재하는 수많은 정보 자원의 위치를 정확하고 편리하게 표현하기 위한 방법</td></tr><tr><td>642</td><td>미국</td><td>USA</td><td>USA</td><td>북아메리카 대륙의 가운데를 차지하는 연방 공화국</td></tr><tr><td>643</td><td>사용</td><td>USE</td><td>use</td><td>일정한 목적이나 기능에 맞게 씀</td></tr><tr><td>644</td><td>사용자</td><td>USER</td><td>user</td><td>컴퓨터 시스템 또는 정보 통신 시스템이 제공하는 서비스를 이용하는 사람</td></tr><tr><td>645</td><td>사용처</td><td>USPL</td><td>use place</td><td>돈이나 물건 따위가 쓰인 곳. 또는 쓰이는 곳.</td></tr><tr><td>646</td><td>값</td><td>VAL</td><td>value</td><td>코드에 해당하는 값</td></tr><tr><td>647</td><td>유효</td><td>VALI</td><td>VALIDITY</td><td>보람이나 효과가 있음</td></tr><tr><td>648</td><td>VAN</td><td>VAN</td><td>value added network</td><td>부가가치통신망.공중 전기통신사업자(예컨대 한국전기통신공사)로부터 통신회선을 차용하여 독자적인 네트워크를 형성하는 것.</td></tr><tr><td>649</td><td>변수</td><td>VARI</td><td>variable</td><td>어떤 관계나 범위 안에서 여러 가지 값으로 변할 수 있는 수</td></tr><tr><td>650</td><td>부가가치세</td><td>VAT</td><td>VALUE ADDED TAX</td><td>국세 또는 지방세를 본세(本稅)로 하여 지방 자치 단체가 다시 첨가하여 부과하던 세금</td></tr><tr><td>651</td><td>버전</td><td>VER</td><td>VERSION</td><td>어떤 프로그램을 수정, 개선하여 완성한 것. 새로워질 때마다 번호를 늘려 나감</td></tr><tr><td>652</td><td>뷰</td><td>VIEW</td><td>view</td><td>컴퓨터 그래픽스에서 3차원 객체의 가능한 표현 가운데 하나.</td></tr><tr><td>653</td><td>VIP</td><td>VIP</td><td>very important person</td><td>브이아이피. 중요 인물. 요인. 특별히 대우해야 할 사람</td></tr><tr><td>654</td><td>가상</td><td>VIR</td><td>virtual</td><td>사실이 아니거나 사실 여부가 분명하지 않은 것을 사실이라고 가정하여 생각함</td></tr><tr><td>655</td><td>비자</td><td>VISA</td><td>VISA</td><td>외국인에 대한 출입국 허가의 증명</td></tr><tr><td>656</td><td>VOC</td><td>VOC</td><td>voice of customer</td><td>다양한 채널을 통하여 유입되는 고객의 소리를 통합하여 관련정보와 업무를 통합 제공하여 업무 프로세스, 마케팅 등의 효율성을 극대화 할 수 있는 시스템 을 말합니다.</td></tr><tr><td>657</td><td>VOD</td><td>VOD</td><td>vedio on demand</td><td>주문형 비디오 시스템. 영화와 같은 영상, 음성, 정보 등을 시청자가 원하는 시간에 원하는 내용의 프로그램을 전송, 재생해주는 시스템</td></tr><tr><td>658</td><td>음성</td><td>VOIC</td><td>voice</td><td>목소리, 음성, 말로 표현되는 소리</td></tr><tr><td>659</td><td>투표</td><td>VOTE</td><td>VOTE</td><td>선거를 하거나 가부를 결정할 때에 의사를 표시하는 일 또는 그런 표. ex) 찬반 투표</td></tr><tr><td>660</td><td>VR</td><td>VR</td><td>VR</td><td>가상 현실 (Virtual Reality)의 줄임말</td></tr><tr><td>661</td><td>VVIP</td><td>VVIP</td><td>VVIP</td><td>한섬온오프라인고객 최상위 등급</td></tr><tr><td>662</td><td>시청자</td><td>VWMN</td><td>viewer, seeing and hearing man</td><td>텔레비전의 방송 프로그램을 시청하는 사람</td></tr><tr><td>663</td><td>허리</td><td>WAIST</td><td>waist</td><td>사람이나 동물의 갈빗대 아래에서부터 엉덩이까지의 잘록한 부분. 사물의 가운데 부분.</td></tr><tr><td>664</td><td>대기</td><td>WAIT</td><td>wait</td><td>때나 기회를 기다림</td></tr><tr><td>665</td><td>경고</td><td>WARN</td><td>warning</td><td>조심하거나 삼가도록 미리 주의를 줌</td></tr><tr><td>666</td><td>웹진</td><td>WBZN</td><td>WEBZINE</td><td>웹매거진, 웹에서 제공되어지는 잡지</td></tr><tr><td>667</td><td>평일</td><td>WD</td><td>WEEK OF DAY</td><td>특별한 일이 없는 보통 때. 토요일, 일요일, 공휴일이 아닌 보통 날.</td></tr><tr><td>668</td><td>요일</td><td>WDAY</td><td>Day-Of-Week</td><td>일주일의 각 날을 이르는 말</td></tr><tr><td>669</td><td>탈퇴</td><td>WDRA</td><td>withdrawal</td><td>관계하고 있던 조직이나 단체 따위에서 관계를 끊고 물러남</td></tr><tr><td>670</td><td>웹</td><td>WEB</td><td>web</td><td>웹이란 월드 와이드 웹(world wide web)의 준말로서, 최근 인터넷에서 가장 인기가 끄는 정보연결서비스. 웹은 인터넷상의 다른 서비스와는 달리 문자 위주의 서비스에서 탈피하여, 문자·영상·음성등이 혼합된 멀티미디어 정보를 마치 거미줄과 같은 통신망으로 세계 각지에 연결시켜 주는 서비스</td></tr><tr><td>671</td><td>수요일</td><td>WED</td><td></td><td></td></tr><tr><td>672</td><td>무게</td><td>WEGT</td><td>weight</td><td>물건의 무거운 정도. 사물이 지닌 가치나 중요성의 정도. 사람 됨됨이의 침착하고 의젓한 정도.</td></tr><tr><td>673</td><td>가중</td><td>WETG</td><td>weighting</td><td>책임이나 부담 등을 더 무겁게 함</td></tr><tr><td>674</td><td>중량</td><td>WG</td><td>Weight</td><td>물건의 무거운 정도</td></tr><tr><td>675</td><td>몸무게</td><td>WGT</td><td>WEIGHT</td><td>몸의 무게.</td></tr><tr><td>676</td><td>창고</td><td>WH</td><td>warehouse</td><td>재화(財貨)의 보관을 위해 시설된 건물 또는 설비</td></tr><tr><td>677</td><td>너비</td><td>WIDTH</td><td>WIDTH</td><td>평면이나 넓은 물체의 가로로 건너지른 거리.</td></tr><tr><td>678</td><td>당첨</td><td>WIN</td><td>win a prize</td><td>추첨에서 뽑힘</td></tr><tr><td>679</td><td>당첨자</td><td>WINMN</td><td>the persion win a prize</td><td>추첨에서 뽑힌 사람</td></tr><tr><td>680</td><td>위시</td><td>WISH</td><td>WISH</td><td>원하는(바라는) 것, 의도, 소망</td></tr><tr><td>681</td><td>주간</td><td>WK</td><td>week</td><td>객체의 소유주</td></tr><tr><td>682</td><td>무단</td><td>WO</td><td>without</td><td>허가없이 업무를 처리함을 의미한다.</td></tr><tr><td>683</td><td>여성</td><td>WOMAN</td><td>woman</td><td>성(性)의 측면에서 여자를 이르는 말</td></tr><tr><td>684</td><td>용어</td><td>WORD</td><td>wording</td><td>상품기술서에서 상품 설명시 사용되는 단어 일정한 전문 분야에서 주로 사용하는 말</td></tr><tr><td>685</td><td>근무</td><td>WRKG</td><td>WORKING</td><td>직장에 적(籍)을 두고 직무에 종사함</td></tr><tr><td>686</td><td>작업</td><td>WORK</td><td>WORK</td><td>일을 함. 또는 그 일. 일정한 목적과 계획 아래 하는 일</td></tr><tr><td>687</td><td>근무처</td><td>WPL</td><td>work place</td><td>근무하는 일정한 기관이나 부서</td></tr><tr><td>688</td><td>문구</td><td>WRD</td><td>words</td><td>글의 구절. words;the[a] wording;a phrase;an expression</td></tr><tr><td>689</td><td>쓰기</td><td>WRITE</td><td>Write</td><td>게시판에 쓰기권한 여부를 check하기 위한 단어</td></tr><tr><td>690</td><td>작성</td><td>WRT</td><td>write</td><td>서류, 원고, 계획 따위를 만듦</td></tr><tr><td>691</td><td>작성자</td><td>WRTMN</td><td>write man</td><td>서류, 원고, 계획 따위를 만드는 자</td></tr><tr><td>692</td><td>회수</td><td>WTHD</td><td>withdrawal</td><td>도로 거두어들임</td></tr><tr><td>693</td><td>이내</td><td>WTHN</td><td>whin</td><td>장소·시간·거리를 나타내는 말과 함께 써서 ~이내, ~범위내</td></tr><tr><td>694</td><td>대기자</td><td>WTPS</td><td>WAITING PERSON</td><td>때나 기회를 기다리는 사람</td></tr><tr><td>695</td><td>XML</td><td>XML</td><td>XML</td><td>확장 가능 마크 업 언어(Extensible Mark-up Language: 컴퓨터 텍스트 구조 표시 시스템)</td></tr><tr><td>696</td><td>년차</td><td>YEAR_CNT</td><td>YEAR COUNT</td><td>햇수의 차례</td></tr><tr><td>697</td><td>연말</td><td>YEND</td><td>year end</td><td>한 해의 마지막 무렵을 의미함</td></tr><tr><td>698</td><td>년월</td><td>YM</td><td>Year-Month</td><td>특정 연도와 월을 아울러 이르는 말</td></tr><tr><td>699</td><td>여부</td><td>YN</td><td>yes or no</td><td>그러함과 그러하지 아니함.</td></tr><tr><td>700</td><td>유무</td><td>EN</td><td>EXISTENCE AND NONEXISTENCE</td><td>있음과 없음을 구분하기 위해 사용</td></tr><tr><td>701</td><td>년</td><td>YY</td><td>year</td><td>해를 세는 단위.</td></tr><tr><td>702</td><td>년도</td><td>YYYY</td><td>year</td><td>형식 : YYYY. 해를 뜻하는 숫자 뒤에 쓰여서 일정한 기간 단위로서의 그해를 나타낼 때는 년도 (2008년도) 편의상 구분한 일 년 동안의 기간을 나타낼 때는 연도 (회계연도, 계약연도)</td></tr><tr><td>703</td><td>우편번호</td><td>ZIP_NO</td><td>ZIP_NO</td><td></td></tr><tr><td>704</td><td>보유</td><td>POSN</td><td>possession</td><td>가지고 있거나 간직하고 있음</td></tr><tr><td>705</td><td>사업자등록번호</td><td>BMAN_NO</td><td>coporation registration number</td><td>사업체를 표시하거나 상거래시 사용되는 사업체 고유번호. 모두 10개의 숫자가 ○○○-○○-○○○○○ 형태로 표기되는데, 각 자리의 수는 각각 독특한 사항을 숫자화한 것.</td></tr><tr><td>706</td><td>산</td><td>MNTN</td><td>MOUNTAIN</td><td>평지보다 높이 솟아 있는 땅의 부분</td></tr><tr><td>707</td><td>시군구</td><td>GUGUN</td><td>SI GUN GU</td><td>행정 구역의 시도와 군 그리고 구를 통합하여 일컫는 말</td></tr><tr><td>708</td><td>아이템</td><td>AITEM</td><td>AITEM</td><td>패션과 관계된 의류 및 액세서리류의 품목.</td></tr><tr><td>709</td><td>컨텐츠</td><td>CNTNTS</td><td>CONTENTS</td><td>인터넷이나 컴퓨터 통신 등을 통하여 제공되는 각종 정보나 그 내용물</td></tr><tr><td>710</td><td>클레임</td><td>CLAIM</td><td>CLAIM</td><td>무역 거래에서, 수량ㆍ품질ㆍ포장 따위에 계약 위반 사항이 있는 경우에 매주(賣主)에게 손해 배상을 청구하거나 이의를 제기하는 일.</td></tr><tr><td>711</td><td>리플래쉬</td><td>REFRSH</td><td>REFRESH</td><td>생기를 되찾게 하거나 다시 채움</td></tr><tr><td>712</td><td>조회수</td><td>QRY_CNT</td><td>Query COUNT</td><td>인터넷 따위에 올려진 게시물을 확인한 횟수</td></tr><tr><td>713</td><td>추천수</td><td>RCMCNT</td><td>RECOMMANDATION COUNT</td><td>인터넷 따위에 올려진 게시물을 추천한 횟수</td></tr><tr><td>714</td><td>출력수</td><td>PRTCNT</td><td>PRINT COUNT</td><td>문서나 사진 등을 출력한 횟수</td></tr><tr><td>715</td><td>인원수</td><td>PRSCNT</td><td>NUMBER OF PERSON</td><td>사람의 수효</td></tr><tr><td>716</td><td>별점</td><td>STARSCR</td><td>START SCORE</td><td>별 모양의 표로 표시하는 점수</td></tr><tr><td>717</td><td>전시몰</td><td>DPML</td><td>DISPLAY MALL</td><td>매장에 전시되었거나, 리퍼비시 제품 등 주로 중고 전자제품을 파는 웹사이트</td></tr><tr><td>718</td><td>할당량</td><td>QUOTQY</td><td>QUOTA quantity</td><td>할당된 량</td></tr><tr><td>719</td><td>BLUR</td><td>BLUR</td><td>BLUR</td><td>HTML 문장 약어로, 흐릿하게 처리하는 작업을 통칭</td></tr><tr><td>720</td><td>부</td><td>SCND</td><td>secondary</td><td>윗 단계(주)를 보조하는</td></tr><tr><td>721</td><td>컵사이즈</td><td>CUPSZ</td><td>CUP SIZE</td><td>윗가슴 둘레에서 밑가슴 둘레의 차이를 말하며 언더웨어 중 브라의 사이즈를 나타냄</td></tr><tr><td>722</td><td>신장</td><td>HGT</td><td>HEIGHT</td><td>사람이나 동물이 똑바로 섰을 때에 발바닥에서 머리 끝에 이르는 몸의 길이</td></tr><tr><td>723</td><td>게이트</td><td>GATE</td><td>GATE</td><td>(문이 달린) 출입구</td></tr><tr><td>724</td><td>리워드</td><td>RWD</td><td>REWARD</td><td>보상</td></tr><tr><td>725</td><td>추천인</td><td>RCDR</td><td>RECOMMENDER</td><td>추천하거나 받은 사람</td></tr><tr><td>726</td><td>잔여</td><td>RMND</td><td>REMAINING</td><td>남아 있음. 또는 그런 나머지.</td></tr><tr><td>727</td><td>진행중</td><td>PRCSG</td><td>PROCESSING</td><td>진행이 지속적인 상태</td></tr><tr><td>728</td><td>선물하기</td><td>GVGF</td><td>GIVE A GIFT</td><td>선물을 직접 주고 받지 않고 온라인을 통해서 특정인에게 선물을 구입하여 전달할 수 있는 기능</td></tr><tr><td>729</td><td>횟수</td><td>NMB</td><td>NUMBER</td><td>돌아오는 차례의 수효</td></tr><tr><td>730</td><td>횟수</td><td>NMB</td><td>NUMBER</td><td>돌아오는 차례의 수효</td></tr><tr><td>731</td><td>신고</td><td>DCL</td><td>DECLARATION</td><td>기관이나 조직체의 구성원이 윗사람에게 어떤 사실을 보고하거나 알리는 일</td></tr><tr><td>732</td><td>코디</td><td>CODY</td><td>CODY</td><td>의상, 화장, 액세서리, 구두 따위를 전체적으로 조화롭게 갖추어 꾸미는 일</td></tr><tr><td>733</td><td>타임라인</td><td>TMLINE</td><td>TIMELINE</td><td>일이나 계획, 사건 따위를 시간의 경과에 따라 나열하거나 정리해 놓은 것.</td></tr><tr><td>734</td><td>포스팅</td><td>PSTNG</td><td>POSTING</td><td>누리집이나 블로그 등에서 어떤 기사나 사진, 영상 등을 번호 혹은 이름을 붙여 게시하는 행위</td></tr><tr><td>735</td><td>스타일토크</td><td>STYTLK</td><td>STYLE TALK</td><td>이미지/텍스트 기반의 커뮤니티 게시판 일종으로 스타일과 관련된 내용의 커뮤니티 게시판</td></tr><tr><td>736</td><td>코디셋</td><td>CDYST</td><td>CODY SET</td><td>게시글 중 코디셋 유형 – 코디셋 이미지/텍스트 기반의 짧은 메시지</td></tr><tr><td>737</td><td>바우처</td><td>VUCHR</td><td>VOUCHER</td><td>상품구매에 사용할 수 있는 쿠폰 (정액/정률 할인 쿠폰)</td></tr><tr><td>738</td><td>스친</td><td>STYMBR</td><td>STYLE MEMBER</td><td>스타일라이브에서 활동하는 회원들</td></tr><tr><td>739</td><td>배지</td><td>BADGE</td><td>BADGE</td><td>등급을 나타내는 표기</td></tr><tr><td>740</td><td>차단</td><td>BLCKOF</td><td>BLOCK OFF</td><td>다른 것과의 관계나 접촉을 막거나 끊음.</td></tr><tr><td>741</td><td>숨김</td><td>CNCLMNT</td><td>CONCEALMENT</td><td>어떤 사물을 남이 보이지 않게 하거나 어떤 사실이나 행동을 남이 모르게 감추는 행위</td></tr><tr><td>742</td><td>팔로우</td><td>FOLW</td><td>FOLLOW</td><td>따라가다'라는 뜻의 SNS 용어로, 구독과 비슷한 개념 다른 사람을 친구 추가하는 걸 의미</td></tr><tr><td>743</td><td>선호</td><td>PREFE</td><td>PREFERENCE</td><td>여럿 가운데서 특별히 가려서 좋아함</td></tr><tr><td>744</td><td>나의옷장</td><td>MCLSET</td><td>MY CLOSET</td><td>나의 옷장</td></tr><tr><td>745</td><td>무드</td><td>MOOD</td><td>MOOD</td><td>어떤 상황에서 대체적으로 느껴지는 분위기나 기분</td></tr><tr><td>746</td><td>경도</td><td>LNGT</td><td>LONGITUDE</td><td>그리니치를 본초자오선으로 하여 그 서쪽과 동쪽의 위치를 측정하는 것입니다</td></tr><tr><td>747</td><td>위도</td><td>LATT</td><td>LATITUDE</td><td>지도상의 가로선을 말한다</td></tr><tr><td>748</td><td>HTML</td><td>HTML</td><td>Hiper Text MarkUp Language</td><td>인터넷망에서 정보검색 등에 사용되는 컴퓨터 언어. 문자뿐만 아니라 화상이나 음성, 영상을 포함하는 페이지를 표현할 수 있으며 국제표준화기구(ISO)에서 책정한 표준 범용 문서 생성 언어(SGML)를 바탕으로 책정</td></tr><tr><td>749</td><td>단추</td><td>BTTN</td><td>BUTTON</td><td>옷 따위의 두 폭이나 두 짝을 한데 붙였다 떼었다 하는 물건</td></tr><tr><td>750</td><td>활동</td><td>ACT</td><td>ACTIVITY</td><td>몸을 움직여 행동함. 어떤 일의 성과를 거두기 위하여 힘씀.</td></tr><tr><td>751</td><td>확률</td><td>PRBL</td><td>PROBABILITY</td><td>어떤 사상(事象)이 일어날 확실성의 정도, 또는 그것을 나타내는 수치</td></tr><tr><td>752</td><td>주제</td><td>TOPIC</td><td>TOPIC</td><td>대화나 연구 따위에서 중심이 되는 문제</td></tr><tr><td>753</td><td>리액션</td><td>REACTN</td><td>REACTION</td><td>다른 연기자의 대사나 행동에 대해 반사적 작용으로 나타나는 연기.</td></tr><tr><td>754</td><td>1</td><td>1</td><td>1</td><td>숫자 1</td></tr><tr><td>755</td><td>2</td><td>2</td><td>2</td><td>숫자 2</td></tr><tr><td>756</td><td>3</td><td>3</td><td>3</td><td>숫자 3</td></tr><tr><td>757</td><td>4</td><td>4</td><td>4</td><td>숫자 4</td></tr><tr><td>758</td><td>5</td><td>5</td><td>5</td><td>숫자 5</td></tr><tr><td>759</td><td>6</td><td>6</td><td>6</td><td>숫자 6</td></tr><tr><td>760</td><td>7</td><td>7</td><td>SEVEN</td><td>숫자 7</td></tr><tr><td>761</td><td>1</td><td>1</td><td>ONE</td><td>숫자 1</td></tr><tr><td>762</td><td>2</td><td>2</td><td>TWO</td><td>숫자 2</td></tr><tr><td>763</td><td>3</td><td>3</td><td>THREE</td><td>숫자 3</td></tr><tr><td>764</td><td>4</td><td>4</td><td>FOUR</td><td>숫자 4</td></tr><tr><td>765</td><td>5</td><td>5</td><td>FIVE</td><td>숫자 5</td></tr><tr><td>766</td><td>6</td><td>6</td><td>SIX</td><td>숫자 6</td></tr><tr><td>767</td><td>외국</td><td>FFM</td><td>FOREIGN COUNTRY</td><td>자기 나라가 아닌 다른 나라</td></tr><tr><td>768</td><td>국번</td><td>TXNO</td><td>TELEPHONE EXCHANGE NUMBER</td><td>전화 교환국의 국명(局名)을 나타내는 번호</td></tr><tr><td>769</td><td>WMS</td><td>WMS</td><td>WAREHOUSE MANAGEMENT SYSTEM</td><td>창고 관리 시스템(Warehouse Management System)</td></tr><tr><td>770</td><td>송신</td><td>SEND</td><td>SEND</td><td>컴퓨터가 단말 장치 또는 다른 컴퓨터에 전송하기 위해 메시지를 회선에 보내는 과정.</td></tr><tr><td>771</td><td>컨텐츠</td><td>CONT</td><td>CONTENTS</td><td>인터넷이나 컴퓨터 통신 등을 통하여 제공되는 각종 정보나 그 내용물</td></tr><tr><td>772</td><td>업</td><td>UP</td><td>UP</td><td>‘더 높이’, ‘위로’, ‘꼭대기로’의 뜻을 나타냄</td></tr><tr><td>773</td><td>금지</td><td>PHBT</td><td>PROHIBITION</td><td>법이나 규칙이나 명령 따위로 어떤 행위를 하지 못하도록 함</td></tr><tr><td>774</td><td>누적</td><td>ACCM</td><td>ACCUMULATION</td><td>포개어 여러 번 쌓음. 또는 포개져 여러 번 쌓임.</td></tr><tr><td>775</td><td>댓글</td><td>RPL</td><td>REPLY</td><td>인터넷에 오른 원문에 대하여 짤막하게 답하여 올리는 글.</td></tr><tr><td>776</td><td>스타일러</td><td>STYLR</td><td>STYLER</td><td>헤어디자이너</td></tr><tr><td>777</td><td>레드</td><td>RED</td><td>RED</td><td>빨강, 빨간[붉은]색</td></tr><tr><td>778</td><td>e머니</td><td>ECASH</td><td>ELECTRONIC CASH</td><td>전자화폐. 칩이 내장된 카드나 공중정보통신망과 연결된 PC 등의 전자기기에 전자기호 형태로 화폐적 가치를 저장하였다가 상품 등의 구매에 사용할 수 있는 전자 지급 수단</td></tr><tr><td>779</td><td>깜짝미션</td><td>SPRMSN</td><td>SURPRISE MISSION</td><td>이벤트의 일종으로 비정기적으로 발생하는 이벤트</td></tr><tr><td>780</td><td>도움</td><td>ASTNC</td><td>ASSISTANCE</td><td>남을 돕는 일</td></tr><tr><td>781</td><td>집계</td><td>AGRT</td><td>AGGREGATE</td><td>이미 된 계산들을 한데 모아서 계산함. 또는 그런 계산</td></tr><tr><td>782</td><td>전문</td><td>TLGR</td><td>TELEGRAPHIC MESSAGE</td><td>전보의 내용이 되는 글.</td></tr><tr><td>783</td><td>전문</td><td>TLGRM</td><td>TELEGRAPHIC MESSAGE</td><td>전보의 내용이 되는 글.</td></tr><tr><td>784</td><td>롤오버</td><td>RLOVR</td><td>ROLLOVER</td><td>웹 페이지의 이미지나 문장의 어느 부분 위에 마우스를 올려 놓거나 스쳐갈 때 변화가 생기거나 다른 이미지나 웹 페이지로 대체되는 효과</td></tr><tr><td>785</td><td>컬러칩</td><td>CLORCHP</td><td>COLOR CHIP</td><td>색상 용어로 색표(색상표)</td></tr><tr><td>786</td><td>가중치</td><td>WGHT</td><td>WEIGHT</td><td>일반적으로 평균치를 산출할때 개별치에 부여되는 중요도</td></tr><tr><td>787</td><td>취향</td><td>FNDNS</td><td>FONDNESS</td><td>하고 싶은 마음이 생기는 방향. 또는 그런 경향</td></tr><tr><td>788</td><td>검수</td><td>INSP</td><td>INSPECTION</td><td>물건의 규격, 수량, 품질 따위를 검사하여 받음.</td></tr><tr><td>789</td><td>케어</td><td>CARE</td><td>CARE</td><td>돌봄. 보살핌</td></tr><tr><td>790</td><td>세탁비</td><td>LNDRX</td><td>LAUNDRY EXPENSES</td><td>세탁하는 데 드는 돈.</td></tr><tr><td>791</td><td>예상</td><td>EXPCT</td><td>EXPECTATION</td><td>어떤 일을 직접 당하기 전에 미리 생각하여 둠.</td></tr><tr><td>792</td><td>수선비</td><td>RPRGX</td><td>REPAIRING EXPENSES</td><td>낡거나 헌 물건을 고치는 데 드는 비용.</td></tr><tr><td>793</td><td>수거</td><td>PCKUP</td><td>PICKUP</td><td>거두어 감</td></tr><tr><td>794</td><td>수선</td><td>RPRG</td><td>REPAIR</td><td>낡거나 헌 물건을 고침.</td></tr><tr><td>795</td><td>포장</td><td>PCKG</td><td>PACKING</td><td>물건을 싸거나 꾸림</td></tr><tr><td>796</td><td>박스</td><td>BOX</td><td>BOX</td><td>물건을 넣어 두기 위하여 나무, 대나무, 두꺼운 종이 같은 것으로 만든 네모난 그릇. 물건을 ‘상자’에 담아 그 분량을 세는 단위.</td></tr><tr><td>797</td><td>무료</td><td>FREE</td><td>FREE</td><td>요금이 없음</td></tr><tr><td>798</td><td>핀번호</td><td>PINNO</td><td>PIN NUMBER</td><td>사용자를 식별하기 위해 사용하는 보통 4자리에서 길게는 8자리의 짧은 숫자만으로 이루어진 비밀번호</td></tr><tr><td>799</td><td>상품권</td><td>GFTCT</td><td>GIFT CERTIFICATE</td><td>액면 가격에 상당하는 상품과 교환할 수 있는 표</td></tr><tr><td>800</td><td>소명</td><td>EXTNC</td><td>EXTINCTION</td><td>사라져 없어짐</td></tr><tr><td>801</td><td>실환불</td><td>RLRFD</td><td>REAL REFUND</td><td>실제 환불을 의미하는 합성어</td></tr><tr><td>802</td><td>핀번호</td><td>PINNO</td><td>PIN NUMBER</td><td>사용자를 식별하기 위해 사용하는 보통 4자리에서 길게는 8자리의 짧은 숫자만으로 이루어진 비밀번호</td></tr><tr><td>803</td><td>소멸</td><td>EXTNC</td><td>EXTINCTION</td><td>사라져 없어짐</td></tr><tr><td>804</td><td>이력</td><td>HIST</td><td>history</td><td>지금까지 거쳐 온 학업, 직업, 경험 등의 내력</td></tr><tr><td>805</td><td>정보</td><td>INFO</td><td>information</td><td>관찰이나 측정을 통하여 수집한 자료를 실제 문제에 도움이 될 수 있도록 정리한 지식</td></tr><tr><td>806</td><td>색인</td><td>IDX</td><td>INDEX</td><td></td></tr><tr><td>807</td><td>가점</td><td>ADDPNT</td><td>ADD POINTS</td><td>점수를 더함, 또는 그 점수.</td></tr><tr><td>808</td><td>감점</td><td>SBPNT</td><td>SUBTRACT POINTS</td><td>점수가 깎임. 또는 그 점수.</td></tr><tr><td>809</td><td>가점</td><td>ADPNT</td><td>ADD POINTS</td><td>점수를 더함, 또는 그 점수.</td></tr><tr><td>810</td><td>배치</td><td>BATCH</td><td>BATCH JOB</td><td>스케쥴링에 의해 정해진 시간에 서버에서 자동으로 실행시키는 작업</td></tr><tr><td>811</td><td>감점</td><td>SBPNT</td><td>SUBTRACT POINTS</td><td>점수가 깎임. 또는 그 점수.</td></tr><tr><td>812</td><td>가점</td><td>ADPNT</td><td>ADD POINTS</td><td>점수를 더함, 또는 그 점수.</td></tr><tr><td>813</td><td>계정</td><td>ACCT</td><td>ACCOUNT</td><td>인터넷에서 이용자의 신분을 증명할 수 있는 고유의 체계, 문자나 숫자 따위로 이루어 짐</td></tr><tr><td>814</td><td>오프라인</td><td>OFLN</td><td>OFF-LINE</td><td>온라인(on-line)에 상대하여 인터넷과 같은 가상 공간이 아닌 실재하는 공간, 또는 사람들이 실제로 경험하는 현실의 세계를 가리키는 말. 정보·통신 ] 단말기의 입출력 장치 따위가 연결되어 있지 아니하여 중앙 처리 장치의 직접적인 제어를 받지 아니하는 상태.</td></tr><tr><td>815</td><td>구매자</td><td>PRCHSR</td><td>PURCHASER</td><td>물건을 사는 사람이나 단체.</td></tr><tr><td>816</td><td>원거래</td><td>ORGTR</td><td>ORIGINAL TRADE</td><td>본래의 거래를 뜻하는 합성어</td></tr><tr><td>817</td><td>부분</td><td>SCTN</td><td>SECTION</td><td>전체를 이루는 작은 범위. 또는 전체를 몇 개로 나눈 것의 하나.</td></tr><tr><td>818</td><td>개월수</td><td>MNTHNO</td><td>NUMBER OF MONTH</td><td>개월의 수효</td></tr><tr><td>819</td><td>간편</td><td>SMPCT</td><td>SIMPLICITY</td><td>간편하다의 어근</td></tr><tr><td>820</td><td>앱</td><td>APP</td><td>APPLICATION</td><td>응용 기술 (프로그램)을 뜻하는 애플리케이션 (Application)의 줄임말로, 특정 목적을 가지고 제작된 프로그램</td></tr><tr><td>821</td><td>용도</td><td>PURPS</td><td>PURPOSE</td><td>쓰이는 길. 또는 쓰이는 곳</td></tr><tr><td>822</td><td>이니시스</td><td>INICIS</td><td>INICIS</td><td>온라인 결제PG사로 KG이니시스의 줄임말</td></tr><tr><td>823</td><td>엑심베이</td><td>EXIMBAY</td><td>EXIMBAY</td><td>포털/인터넷정보매개 등 포털 및 기타 인터넷 정보매개 서비스업체로 온라인 결제 PG사 중 하나</td></tr><tr><td>824</td><td>LGCNS</td><td>LGCNS</td><td>LGCNS</td><td>LG 계열의 컨설팅, 시스템 통합, 아웃소싱, ERP, IT컨버전스 등의 종합IT서비스 기업으로 온라인 결제PG사 중 하나</td></tr><tr><td>825</td><td>네이버</td><td>NAVER</td><td>NAVER</td><td>인터넷검색사이트 운영,인터넷방송,컨텐츠검색,고도정보통신서비스,게임컨텐츠제공,광고대행 등을 서비스 하는 포털 및 기타 인터넷 정보매개 서비스 업체</td></tr><tr><td>826</td><td>지불</td><td>PYMNT</td><td>PAYMENT</td><td>돈을 내어 줌. 또는 값을 치름.</td></tr><tr><td>827</td><td>환전</td><td>EXCHG</td><td>EXCHANGE</td><td>서로 종류가 다른 화폐와 화폐, 또는 화폐와 지금(地金)을 교환함. 또는 그런 일</td></tr><tr><td>828</td><td>신용</td><td>CRDT</td><td>CREDIT</td><td>사람이나 사물이 틀림없다고 믿어 의심하지 아니함. 또는 그런 믿음성의 정도 거래한 재화의 대가를 앞으로 치를 수 있음을 보이는 능력</td></tr><tr><td>829</td><td>환율</td><td>EXCHRT</td><td>EXCHANGE RATE</td><td>자기 나라 돈과 다른 나라 돈의 교환 비율</td></tr><tr><td>830</td><td>달러</td><td>USD</td><td>UNITED STATES DOLLAR</td><td>달러는 미국 공식 통화로 세계에서 가장 많이 환전되는 통화이다. USD는 달러의 통화 코드이며 기호는 $이며 명목 화폐입니다. 달러 환산 계수의 유효 숫자는 6자리입니다.</td></tr><tr><td>831</td><td>무상</td><td>FRE</td><td>FREE OF COST</td><td>어떤 행위에 대하여 아무런 대가나 보상이 없음</td></tr><tr><td>832</td><td>통신</td><td>CMNCS</td><td>COMMUNICATIONS</td><td>소식을 전함 우편이난 전신, 전화 따위로 정보나 의사를 전달함</td></tr><tr><td>833</td><td>에러</td><td>ERROR</td><td>ERROR</td><td>잘하지 못하여 그릇된 점. 또는 조심하지 아니하여 그르치는 행위. [체육 ] 야구에서, 잡을 수 있는 타구나 송구를 잡지 못하여 주자를 살게 하는 일. [정보·통신 ] 연산 처리 장치의 잘못된 동작이나 소프트웨어의 잘못 때문에 생기는, 계산값과 참값과의 오차.</td></tr><tr><td>834</td><td>에러</td><td>ERROR</td><td>ERROR</td><td>잘하지 못하여 그릇된 점. 또는 조심하지 아니하여 그르치는 행위. [체육 ] 야구에서, 잡을 수 있는 타구나 송구를 잡지 못하여 주자를 살게 하는 일. [정보·통신 ] 연산 처리 장치의 잘못된 동작이나 소프트웨어의 잘못 때문에 생기는, 계산값과 참값과의 오차.</td></tr><tr><td>835</td><td>오류</td><td>ERROR</td><td>ERROR</td><td>잘하지 못하여 그릇된 점. 또는 조심하지 아니하여 그르치는 행위. [체육 ] 야구에서, 잡을 수 있는 타구나 송구를 잡지 못하여 주자를 살게 하는 일. [정보·통신 ] 연산 처리 장치의 잘못된 동작이나 소프트웨어의 잘못 때문에 생기는, 계산값과 참값과의 오차.</td></tr><tr><td>836</td><td>스마일</td><td>SMILE</td><td>SMILE</td><td>소리 없이 빙긋 웃는 것. 또는 그 표정</td></tr><tr><td>837</td><td>체크</td><td>CHK</td><td>CHECK</td><td>사물의 상태를 검사하거나 대조함. 또는 그런 표적으로 찍는 'V' 자 모양의 표.</td></tr><tr><td>838</td><td>빈번호</td><td>BINNO</td><td>Bank Identification Number</td><td>은행이나 카드사의 고유번호</td></tr><tr><td>839</td><td>빈번호</td><td>BINNO</td><td>Bank Identification Number</td><td>은행이나 카드사의 고유번호로 카드 일련번호 16자리 중 처음 6자리를 가리키는 것</td></tr><tr><td>840</td><td>스마일캐시</td><td>SMCASH</td><td>SMILE CASH</td><td>G마켓, 옥션, G9에서 상품 구매 시 현금처럼 사용할 수 있는 결제 수단으로 현금처럼 상품 구매가 가능하고 계좌 이체 방식으로 충전해 두고 사용 할 수 있으며, 이벤트로 지급된 캐시를 제외, 출금 가능한 금액 내에서 내 계좌로 출금도 가능함</td></tr><tr><td>841</td><td>방문</td><td>VISIT</td><td>VISIT</td><td>어떤 사람이나 장소를 찾아가서 만나거나 봄.</td></tr><tr><td>842</td><td>한국</td><td>KOREA</td><td>KOREA</td><td>[역사 ] ‘대한 제국’을 줄여 이르는 말. [지명 ] 아시아 대륙 동쪽에 있는 한반도와 그 부속 도서(島嶼)로 이루어진 공화국</td></tr><tr><td>843</td><td>바이오</td><td>BIO</td><td>BIO</td><td>“생”이나 “생물”을 의미하는 접두어. 그리스어 bios는 생명을 의미</td></tr><tr><td>844</td><td>서버</td><td>SVR</td><td>SERVER</td><td>주된 정보의 제공이나 작업을 수행하는 컴퓨터 시스템.</td></tr><tr><td>845</td><td>기기</td><td>MACH</td><td>MACHINERY</td><td>기구(器具)·기계(器械)·기계(機械)의 총칭.</td></tr><tr><td>846</td><td>산정</td><td>CALCL</td><td>CALCULATION</td><td>셈하여 정함</td></tr><tr><td>847</td><td>소진</td><td>EXHST</td><td>EXHAUSTION</td><td>점점 줄어들어 다 없어짐. 또는 다 써서 없앰.</td></tr><tr><td>848</td><td>연장</td><td>PRLNG</td><td>PROLONGATION</td><td>시간이나 거리 따위를 본래보다 길게 늘림.</td></tr><tr><td>849</td><td>원승인</td><td>ORGAPR</td><td>ORIGINAL APPROVE</td><td>원래 승인의 합성어</td></tr><tr><td>850</td><td>증정</td><td>PRSTN</td><td>PRESENTATION</td><td>어떤 물건 따위를 성의 표시나 축하 인사로 줌.</td></tr><tr><td>851</td><td>증정</td><td>PRESTN</td><td>PRESENTATION</td><td>어떤 물건 따위를 성의 표시나 축하 인사로 줌.</td></tr><tr><td>852</td><td>메시지</td><td>MSG</td><td>message</td><td>언어나 기호에 의하여 전달되는 정보 내용</td></tr><tr><td>853</td><td>결제</td><td>PAY</td><td>PAYMENT</td><td>대금을 주고받아 매매 당사자 사이의 거래 관계를 끝맺는 일</td></tr><tr><td>854</td><td>획득</td><td>ACQSN</td><td>ACQUISITION</td><td>얻어 내거나 얻어 가짐.</td></tr><tr><td>855</td><td>암호</td><td>SCRCD</td><td>SCREAT CODE</td><td>사용자로부터 시스템이나 데이터 파일을 이용할 수 있는 권리를 확인하기 위하여 쓰는 비밀 부호.</td></tr><tr><td>856</td><td>실결제</td><td>RLPAY</td><td>REAL PAY</td><td>실제 결제를 뜻하는 합성어</td></tr><tr><td>857</td><td>암호화</td><td>ENCPT</td><td>ENCRYPTION</td><td>통신할 내용을 일정한 체계에 따라 암호로 바꿈.</td></tr><tr><td>858</td><td>암호화</td><td>ENCPT</td><td>ENCRYPTION</td><td>메시지의 내용이 불명확하도록 평문을 재구성하여 암호문을 만드는 것으로, 암호화 알고리즘을 사용해 메시지를 재구성함</td></tr><tr><td>859</td><td>판</td><td>EDTN</td><td>EDITION</td><td>[매체 ] 그림이나 글씨 따위를 새겨 찍는 데 쓰는 나무나 쇠붙이의 조각. [매체 ] 활자로 짜서 만든 인쇄용 판. 또는 그 판으로 하는 인쇄. [매체 ] 인쇄한 면(面)의 크기</td></tr><tr><td>860</td><td>스탬프</td><td>STMP</td><td>STAMP</td><td>우체국에서 접수된 우편물의 우표 따위에 도장을 찍음. 또는 그 도장. 접수 날짜, 국명(局名) 따위가 새겨져 있다. 명승고적이나 특별한 행사를 기념하기 위하여 찍는 고무도장.</td></tr><tr><td>861</td><td>스탬핑</td><td>STMPNG</td><td>STAMPING</td><td>스탬프를 받는 행위를 일컫는 말</td></tr><tr><td>862</td><td>스탬핑</td><td>STMPNG</td><td>STAMPING</td><td>스탬프를 찍는 행위를 일컫는 말</td></tr><tr><td>863</td><td>소개</td><td>INTRO</td><td>INTRODUCION</td><td>모르는 사이를 알고 지내도록 중간에서 관계를 맺어 줌 잘 알려지지 아니하였거나, 모르는 사실이나 내용을 잘 알도록 하여 주는 설명.</td></tr><tr><td>864</td><td>가로</td><td>WDTH</td><td>WIDTH</td><td>왼쪽과 오른쪽의 방향, 또는 그 길이</td></tr><tr><td>865</td><td>세로</td><td>VRTICL</td><td>VERTICAL</td><td>위에서 아래의 방향으로. 또는 아래로 길게.</td></tr><tr><td>866</td><td>뉴스</td><td>NEWS</td><td>NEWS</td><td>새로운 소식을 전하여 주는 방송의 프로그램. 일반에게 잘 알려지지 아니한 새로운 소식.</td></tr><tr><td>867</td><td>알림톡</td><td>TMS</td><td>NOTICE TALK MANAGEMENT SYSTEM</td><td>주문, 결제, 배송 등 정보성 메시지를 관리하는 시스템으로 SMS, MMS, TCS(채팅)등을 말함</td></tr><tr><td>868</td><td>챗봇</td><td>CHATBOT</td><td>CHATBOT</td><td>문자 또는 음성으로 대화하는 기능이 있는 컴퓨터 프로그램 또는 인공 지능</td></tr><tr><td>869</td><td>신규</td><td>NEW</td><td>NEW</td><td>새로운 규칙이나 규정. 새로이 하는 일.</td></tr><tr><td>870</td><td>도움돼요</td><td>HLPFUL</td><td>HELPFUL</td><td>도움이 되다의 경처체 표현으로 정보가 보탬이 되거나 힘이 되어 준다는 소셜네트워크서비스(SNS) 용어</td></tr><tr><td>871</td><td>도착점</td><td>ARVLP</td><td>ARRIVAL POINT</td><td>다다르기로 목적(목표)한 곳</td></tr><tr><td>872</td><td>처리자</td><td>PROCMN</td><td>PROCESS MAN</td><td>처리를 행하는 사람</td></tr><tr><td>873</td><td>문의자</td><td>INQMN</td><td>INQUIRE MAN</td><td>문의를 하는 사람</td></tr><tr><td>874</td><td>PIN번호</td><td>PINNO</td><td>PIN NUMBER</td><td>사용자를 식별하기 위해 사용하는 보통 4자리에서 길게는 8자리의 짧은 숫자만으로 이루어진 비밀번호</td></tr><tr><td>875</td><td>리오더</td><td>REORD</td><td>REORDER</td><td>출시 되었던 오리지널 제품 중 반응이 좋았던 것을 그 디자인 그대로 다시 만드는 것을 의미함</td></tr><tr><td>876</td><td>동영상</td><td>MPIC</td><td>MOVIE PICTURE</td><td>컴퓨터 모니터의 화상이 텔레비전의 화상처럼 움직이는 것.</td></tr><tr><td>877</td><td>디자이너노트</td><td>DNOTE</td><td>DESIGNER NOTE</td><td>디자이너 노트</td></tr><tr><td>878</td><td>위클리픽</td><td>WKLYPCK</td><td>WEEKLY PIC</td><td>더 한섬 닷컴에서 사용되는 더 메거진의 일종으로 주간별 특정 상품을 보여주는 기능</td></tr><tr><td>879</td><td>즐겨찾기</td><td>BKMK</td><td>BOOK MARK</td><td>인터넷의 웹브라우저에서 웹사이트의 주소를 등록해 놓고 나중에 바로 찾아 갈 수 있도록 하는 기능</td></tr><tr><td>880</td><td>아이콘</td><td>ICON</td><td>ICON</td><td>컴퓨터에 제공하는 명령을 문자나 그림으로 나타낸 것. 마우스나 라이트 펜으로 그림을 선택하여 명령을 실행한다.</td></tr><tr><td>881</td><td>명의자</td><td>NMNE</td><td>NOMINEE</td><td>어떤 사업에서 개인이나 단체를 대표하여 명의를 내세운 사람</td></tr><tr><td>882</td><td>마지막</td><td>LAST</td><td>THE LAST</td><td>시간상이나 순서상의 맨 끝</td></tr><tr><td>883</td><td>도시</td><td>CITY</td><td>CITY</td><td>일정한 지역의 정치ㆍ경제ㆍ문화의 중심이 되는, 사람이 많이 사는 지역</td></tr><tr><td>884</td><td>첫번째</td><td>FIRST</td><td>FIRST</td><td>시간적으로나 순서상으로 맨 앞.</td></tr><tr><td>885</td><td>주</td><td>STATE</td><td>STATE</td><td>연방 국가의 행정 구역의 하나.</td></tr><tr><td>886</td><td>앞</td><td>FRNT</td><td>FRONT</td><td>향하고 있는 쪽이나 곳. 차례나 열에서 앞서는 곳.</td></tr><tr><td>887</td><td>뒤</td><td>BACK</td><td>BACK</td><td>향하고 있는 방향과 반대되는 쪽이나 곳. 시간이나 순서상으로 다음이나 나중.</td></tr><tr><td>888</td><td>생체인식</td><td>BIOM</td><td>BIOMETRICS</td><td>지문, 얼굴, 홍채, 정맥, 목소리와 같이 사람마다 다른 개인의 독특한 생체 정보를 자동화된 장치로 측정하여 정보화하는 보안 인증 방식.</td></tr><tr><td>889</td><td>큐</td><td>QUE</td><td>QUEUE</td><td>[명사][컴퓨터] 큐, 대기 행렬</td></tr><tr><td>890</td><td>세세</td><td>DTLS</td><td>DETAILS</td><td>매우 자세히</td></tr><tr><td>891</td><td>철회</td><td>WDRW</td><td>WITHDRAWAL</td><td>이미 제출하였던 것이나 주장하였던 것을 다시 회수하거나 번복함. 카운트다운을 하는 동안 이미 행해진 일을 취소하는 일.</td></tr><tr><td>892</td><td>마이그</td><td>MIG</td><td>MIGRATION</td><td>[명사] (사람·철새·동물의 대규모) 이주[이동] [명사] (컴퓨터 시스템·프로그램의) 이송[이행(移行)]</td></tr><tr><td>893</td><td>조사</td><td>IVSTG</td><td>INVESTIGATION</td><td>사물의 내용을 명확히 알기 위히야 자세히 살펴보거나 찾아봄</td></tr><tr><td>894</td><td>의견</td><td>OPNIN</td><td>OPINION</td><td>어떤 대상에 대하여 가지는 생각.</td></tr><tr><td>895</td><td>필터</td><td>FILT</td><td>FILTER</td><td>필터(filter) 또는 여과기(濾過器)는 무언가를 걸러내는 도구, 즉 특정 성질을 가진 것은 차단하고, 그렇지 않은 것은 통과시키는 도구</td></tr><tr><td>896</td><td>GNB</td><td>GNB</td><td>Global Navigation Bar</td><td></td></tr><tr><td>897</td><td>컴퍼넌트</td><td>CMPT</td><td>COMPONENT</td><td></td></tr><tr><td>898</td><td>뷰</td><td>VUE</td><td>view</td><td>컴퓨터 그래픽스에서 3차원 객체의 가능한 표현 가운데 하나.</td></tr><tr><td>899</td><td>더미</td><td>DMY</td><td>DUMMY</td><td></td></tr><tr><td>900</td><td>검색어</td><td>SCH_WRD</td><td>search word</td><td></td></tr><tr><td>901</td><td>수거지</td><td>CLTPL</td><td>CLTPL</td><td>고객으로부터 반품 상품을 수거할 곳</td></tr><tr><td>902</td><td>CTI</td><td>CTI</td><td>COMPUTER TELEPHONY INTEGRATION</td><td>컴퓨터와 전화시스템을 통합해 컴퓨터의 컨트롤과 기능을 전화기에 적용시킨 것으로 사용자에게 구내로 들어오는 호출에 대해 더 많은 정보를 제공하고 정보를 분산시키는 기술</td></tr><tr><td>903</td><td>콜백</td><td>CLBC</td><td>CALLBACK</td><td>콜백</td></tr><tr><td>904</td><td>콜센타</td><td>CCTR</td><td>CALL CENTER</td><td>고객과의 커뮤니케이션을 통해 고객의 문제 해결을 유도하고 고객의 다양한 서비스 요청에 대해서도 상시적으로 응대하는 업무를 수행하는 곳</td></tr><tr><td>905</td><td>비중</td><td>WGT</td><td>WEIGHT</td><td></td></tr><tr><td>906</td><td>증감</td><td>INCDEC</td><td></td><td>늚과 줆. 늘림과 줄임</td></tr><tr><td>907</td><td>부여</td><td>GRNT</td><td></td><td>나누어 줌</td></tr><tr><td>908</td><td>전일</td><td>PRDD</td><td>PREVIOS DAY</td><td>이전 일</td></tr><tr><td>909</td><td>웰컴</td><td>WLCM</td><td>welcome</td><td></td></tr><tr><td>910</td><td>기프트</td><td>GIFT</td><td>GIFT</td><td></td></tr><tr><td>911</td><td>카카오</td><td>kakao</td><td>kakao</td><td></td></tr><tr><td>912</td><td>엑셀</td><td>EXL</td><td>EXCEL</td><td>마이크로소프트 오피스 상품 계열 중 하나</td></tr><tr><td>913</td><td>일괄</td><td>BAT</td><td>BATCH</td><td>개별적인 여러 가지 것을 한데 묶음.</td></tr><tr><td>914</td><td>전체</td><td>FULL</td><td></td><td>대상의 형태나 범위를 이루는 것의 모두. 전부.</td></tr><tr><td>915</td><td>성공</td><td>SUCS</td><td></td><td>뜻을 이룸</td></tr><tr><td>916</td><td>행</td><td>ROW</td><td></td><td></td></tr><tr><td>917</td><td>푸시</td><td>PUSH</td><td>PUSH</td><td></td></tr><tr><td>918</td><td>아코디언</td><td>ACDN</td><td>Accordion</td><td></td></tr><tr><td>919</td><td>합</td><td>CMB</td><td>combine</td><td></td></tr><tr><td>920</td><td>담당</td><td>ASGD</td><td></td><td></td></tr><tr><td>921</td><td>즉시</td><td>IMMED</td><td></td><td></td></tr><tr><td>922</td><td>일요일</td><td>SUN</td><td></td><td></td></tr><tr><td>923</td><td>화요일</td><td>TUES</td><td></td><td></td></tr><tr><td>924</td><td>금요일</td><td>FRI</td><td></td><td></td></tr><tr><td>925</td><td>월요일</td><td>MOND</td><td></td><td></td></tr><tr><td>926</td><td>JSON</td><td>JSON</td><td></td><td></td></tr><tr><td>927</td><td>단말기</td><td>TMNL</td><td>TERMINAL</td><td>데이타나 메시지의 송수신을 목적으로 전기통신 네트워크에 접속시키고 있는 단말장치</td></tr><tr><td>928</td><td>스와이프</td><td>SWIPE</td><td>SWIPE</td><td>터치스크린에 손가락을 댄 상태로 화면을 쓸어 넘기거나 손가락을 떼지 않고 정보를 입력하는 일</td></tr><tr><td>929</td><td>시</td><td>HH</td><td>Hour</td><td>시각을 나타내는 단위, 시를 표현, HH24 (예 : 23)</td></tr><tr><td>930</td><td>분</td><td>MI</td><td></td><td>시간 단위의 한 가지</td></tr><tr><td>931</td><td>대시보드</td><td>DSBD</td><td>DASHBOARD</td><td>대시보드란 다양한 데이터를 동시에 비교할 수 있게 해 주는 여러 뷰의 모음</td></tr><tr><td>932</td><td>통신사</td><td>TELCO</td><td></td><td></td></tr><tr><td>933</td><td>명</td><td>NM</td><td>name</td><td>‘이름’의 뜻을 나타내는 말.</td></tr><tr><td>934</td><td>이름</td><td>NM</td><td>name</td><td>‘이름’의 뜻을 나타내는 말.</td></tr><tr><td>935</td><td>나이</td><td>AGE</td><td>AGE</td><td>사람이나 동ㆍ식물 따위가 세상에 나서 살아온 햇수.</td></tr><tr><td>936</td><td>연령</td><td>AGE</td><td>AGE</td><td>사람이나 동ㆍ식물 따위가 세상에 나서 살아온 햇수.</td></tr><tr><td>937</td><td>동의어</td><td>SYN</td><td>synonym</td><td></td></tr><tr><td>938</td><td>신조어</td><td>NOGM</td><td>Neologism</td><td></td></tr><tr><td>939</td><td>문구</td><td>WRD</td><td>WORD</td><td></td></tr><tr><td>940</td><td>문구</td><td>WRD</td><td>words</td><td>글의 구절. words;the[a] wording;a phrase;an expression</td></tr><tr><td>941</td><td>단어</td><td>WRD</td><td>words</td><td>글의 구절. words;the[a] wording;a phrase;an expression</td></tr><tr><td>942</td><td>대사</td><td>COPR</td><td>COMPARISON</td><td></td></tr><tr><td>943</td><td>조정</td><td>AJST</td><td>ADJUSTMENT</td><td></td></tr><tr><td>944</td><td>가이드</td><td>GDE</td><td>GUIDE</td><td></td></tr><tr><td>945</td><td>열번호</td><td>COL_NO</td><td>COLUMN NUMBER</td><td></td></tr><tr><td>946</td><td>랭킹</td><td>RANK</td><td>RAKING</td><td></td></tr><tr><td>947</td><td>월</td><td>MM</td><td>MONTH</td><td></td></tr><tr><td>948</td><td>정기</td><td>RGLR</td><td>REGULAR</td><td>정해진 시기</td></tr><tr><td>949</td><td>다음</td><td>NXT</td><td>NEXT</td><td>어떤 차례의 바로 뒤</td></tr><tr><td>950</td><td>건너뛰기</td><td>SKIP</td><td>SKIP</td><td></td></tr><tr><td>951</td><td>구좌</td><td>ADUT</td><td>ADVERTISING UNIT</td><td>광고에서 기본단위</td></tr><tr><td>952</td><td>품의</td><td>CNFR</td><td>CONFER</td><td>(웃어른이나 상사에게) 말이나 글로 여쭈어 의논하는 것</td></tr><tr><td>953</td><td>반려</td><td>RTRN</td><td>RETURN</td><td></td></tr><tr><td>954</td><td>데이터</td><td>DATA</td><td>DATA</td><td>이론을 세우는 데 기초가 되는 사실. 또는 바탕이 되는 자료. 2 관찰이나 실험, 조사로 얻은 사실이나 정보. ‘자료’로 순화. 3 &#x3C;컴퓨터>컴퓨터가 처리할 수 있는 문자, 숫자, 소리, 그림 따위의 형태로 된 정보.</td></tr><tr><td>955</td><td>동기화</td><td>SYNC</td><td>SYNCHRONIZATION</td><td>시점을 같게 만듬</td></tr><tr><td>956</td><td>UUID</td><td>UUID</td><td></td><td></td></tr><tr><td>957</td><td>부가세</td><td>VAT</td><td></td><td>부가가치세</td></tr><tr><td>958</td><td>링크</td><td>LNK</td><td>LINK</td><td>컴퓨터상에서 어떤 대상에의 연결이나 그와 연관한 복사본 가르킴</td></tr><tr><td>959</td><td>단일</td><td>SNG</td><td>SINGLE</td><td></td></tr><tr><td>960</td><td>심사</td><td>EXAN</td><td>EXAMINATION</td><td></td></tr><tr><td>961</td><td>업데이트</td><td>UPD</td><td>UPDATE</td><td></td></tr><tr><td>962</td><td>스토어</td><td>STO</td><td>STORE</td><td></td></tr><tr><td>963</td><td>미준수</td><td>NOBSN</td><td>Non observance</td><td>(규칙이나 명령 따위를) 그대로 좇아서 지키지 않음</td></tr><tr><td>964</td><td>환산</td><td>CVRT</td><td>CONVERT</td><td>단위가 다른 수량으로 고쳐 계산함</td></tr><tr><td>965</td><td>거부</td><td>VETO</td><td>VETO1</td><td>승낙하지 않음. 동의하지 아니하고 물리침</td></tr><tr><td>966</td><td>반려자</td><td>RTRNMN</td><td>RETURN MAN</td><td></td></tr><tr><td>967</td><td>준수</td><td>OBSN</td><td>OBSN</td><td></td></tr><tr><td>968</td><td>이행</td><td>TRSR</td><td>TRANSFER</td><td>실제로 함. 말과 같이 함</td></tr><tr><td>969</td><td>리드타임</td><td>LT</td><td>Lead Time</td><td>리드 타임 ((기획에서 제품화까지의 소요 시간, 발주에서 배달까지의 시간, 기획에서 실시까지의 준비 기간))</td></tr><tr><td>970</td><td>HOT</td><td>HOT</td><td>HOT</td><td></td></tr><tr><td>971</td><td>방지</td><td>PRVN</td><td>PREVENTION</td><td>어떤 일이 일어나지 않도록 막음</td></tr><tr><td>972</td><td>MO</td><td>MO</td><td>Mobile</td><td></td></tr><tr><td>973</td><td>해시</td><td>HASH</td><td>HASH</td><td></td></tr><tr><td>974</td><td>신고자</td><td>DCLMN</td><td></td><td></td></tr><tr><td>975</td><td>e쿠폰</td><td>ECPN</td><td>e coupon</td><td></td></tr><tr><td>976</td><td>오픈</td><td>OPEN</td><td>OPEN</td><td></td></tr><tr><td>977</td><td>셀러툴사</td><td>STCL</td><td>Seller Tool CO., LTD</td><td></td></tr><tr><td>978</td><td>호출</td><td>CALO</td><td>CALL OUT</td><td></td></tr><tr><td>979</td><td>메이저</td><td>MJR</td><td>MAJOR</td><td></td></tr><tr><td>980</td><td>마이너</td><td>MNR</td><td>MINOR</td><td></td></tr><tr><td>981</td><td>영역</td><td>AREA</td><td>AREA</td><td>구역, 범위</td></tr><tr><td>982</td><td>e티켓</td><td>ETCKT</td><td>TICKET</td><td></td></tr><tr><td>983</td><td>장바구니</td><td>BSKET</td><td>shopping basket</td><td>구매하고자 하는 상품을 모아놓은 공간</td></tr><tr><td>984</td><td>바구니</td><td>BSKET</td><td>shopping basket</td><td>구매하고자 하는 상품을 모아놓은 공간</td></tr><tr><td>985</td><td>예산</td><td>BDGT</td><td>BUDGET</td><td>지정한 기간의 세입 ·세출에 관한 예정계획서</td></tr><tr><td>986</td><td>응답</td><td>RPLY</td><td>REPLY</td><td>부름이나 물음에 응하여 답함</td></tr><tr><td>987</td><td>회신</td><td>RPLY</td><td>REPLY</td><td>부름이나 물음에 응하여 답함</td></tr><tr><td>988</td><td>수령</td><td>RCV</td><td>receipt</td><td>돈이나 물품을 받아들임</td></tr><tr><td>989</td><td>받은</td><td>RCV</td><td>receipt</td><td>돈이나 물품을 받아들임</td></tr><tr><td>990</td><td>발송</td><td>SND</td><td>send, dispatch</td><td>물건, 편지, 서류 따위를 우편이나 운송 수단을 이용하여 보냄</td></tr><tr><td>991</td><td>보낸</td><td>SND</td><td>send, dispatch</td><td>물건, 편지, 서류 따위를 우편이나 운송 수단을 이용하여 보냄</td></tr><tr><td>992</td><td>저장</td><td>SAVE</td><td>SAVE</td><td>문서, 파일을 기록하는 행위</td></tr><tr><td>993</td><td>통계</td><td>STATS</td><td>statistics</td><td>수치 데이터를 수집, 분석 및 해석하는관행 또는 과학</td></tr><tr><td>994</td><td>분석</td><td>ANLY</td><td>analysis</td><td>복잡한 현상이나 대상 또는 개념을, 그것을 구성하는 단순한 요소로 분해하는 일</td></tr><tr><td>995</td><td>Q&#x26;A</td><td>qna</td><td>question and answer</td><td>질의응답</td></tr><tr><td>996</td><td>권한</td><td>AUTH</td><td>authority</td><td>어떤 사람이나 기관의 권리나 권력이 미치는 범위</td></tr></tbody></table>


# Framework 가이드

## 개요

X2BEE는 클라우드가 제공하는 민첩성, 가용성, 확장성의 장점을 최대한 반영하고, 서비스의 개발/운영/관리를 위한 시스템환경 운영에 있어서 클라우드 네이티브의 장점을 최대한 적용하였습니다.

이 가이드를 통해 X2BEE Framework의 핵심 개념과 사용법을 습득하고, 프로젝트 개발시 높은 효율성과 품질을 유지할 수 있을 것입니다.

***

## 문서 구성

<details>

<summary><a href="https://tech.x2bee.com/space/TG/3408404">Framework 아키텍처</a> </summary>

X2BEE Framework의 아키텍처에 대한 개요를 제공합니다. 아키텍처는 Framework의 구조와 컴포넌트 간의 상호 작용을 설명합니다.

</details>

<details>

<summary><a href="/pages/GGYBgXHKL7NJojZmUATa#api-server">API Server 구조도 </a></summary>

API 서버의 구조와 기능을 자세히 살펴봅니다. API 서버의 주요 구성 요소와 데이터 흐름을 이해할 수 있습니다.

</details>

<details>

<summary><a href="/pages/c7635a19ede45a4574ad2c8c34030352a3e2b1d8">Database</a> </summary>

데이터베이스 관련 사항을 다룹니다. 데이터베이스 설정, 연동 및 관리 방법에 대한 정보를 제공합니다.

</details>

<details>

<summary><a href="/pages/cf955333bc5bde86c33ede3bdc0b5da233dee92e">Data Validation </a></summary>

데이터 유효성 검사에 대한 가이드를 제공합니다. 사용자 입력 및 데이터 처리 과정에서 데이터의 유효성을 어떻게 검사할지 설명합니다.

</details>

<details>

<summary><a href="/pages/6fb176f3c2d63ee18e18c44c42e842aa9a90dae9">Exception Handling</a> </summary>

예외 처리 및 오류 관리에 대한 내용을 다룹니다. 프로젝트에서 예외를 어떻게 처리하고 오류를 관리하는지에 대한 정보를 제공합니다.

</details>

<details>

<summary><a href="/pages/7ed9e0e1f583650d81c258d7fa3a83259b682dd4">HTTP Status Code</a> </summary>

HTTP 상태 코드에 대한 설명과 각 코드의 의미를 제공합니다. 웹 애플리케이션에서 클라이언트와 상호 작용할 때 상태 코드를 어떻게 사용할지 알려줍니다.

</details>

<details>

<summary><a href="/pages/cc249793bb0d2e5b1f1e655daeb04156c30c0ecd">로깅(Logging) 설정</a> </summary>

로깅에 대한 설정 및 관리 방법을 다룹니다. 애플리케이션 로그를 생성하고 관리하는 방법을 설명합니다.

</details>

<details>

<summary><a href="/pages/5283d213065c6159c0c66b3896d1876eeb73c801">Query Logging</a> </summary>

쿼리 로깅에 대한 내용을 다룹니다. 데이터베이스 쿼리 실행 로그를 어떻게 활용할지에 대한 정보를 제공합니다.

</details>

{% hint style="info" %}
각 문서의 상세 내용은 X2BEE의 기술적인 세부 정보를 포함하고 있으며, 프로젝트 개발을 지원하기 위한 자세한 내용을 다루고 있으며해당 자료는 파트너 및 개발자를 위해 제공되고 있습니다.
{% endhint %}


# Framework 아키텍처

개발 프레임워크는 MSA 구조를 위해 UI 서비스와 비즈니스 로직을 담당하는 API 서비스 어플리케이션을 분리 합니다.​

UI 서비스 구조는 Spring Boot 기반으로 Next.js를 포함한 View 레이어로 구성 합니다.​

비즈니스 API 서버와 Restful 통신을 위한 Async, non-Blocking 을 지원하는 Web Client를 중심으로 구성 합니다.​

***

### 아키텍처 <a href="#undefined" id="undefined"></a>

#### Front / BackOffice 모듈별 구성도 <a href="#front-backoffice" id="front-backoffice"></a>

<figure><img src="/files/I2YlJ2jn6Z7SlCDC9Py5" alt=""><figcaption></figcaption></figure>

<figure><img src="https://tech.x2bee.com/download/attachments/3408404/Untitled%20Diagram.drawio.png" alt=""><figcaption></figcaption></figure>

#### Front / Backend Layer 별 모듈 구성도 <a href="#front-backend-layer" id="front-backend-layer"></a>

<figure><img src="/files/cf7ROZ9m9dxbXS3H6pZF" alt=""><figcaption></figcaption></figure>

<figure><img src="https://tech.x2bee.com/download/attachments/3408404/framework%20%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%202024%20%EB%A0%88%EC%9D%B4%EC%96%B4%EB%B3%84%20%EB%AA%A8%EB%93%88%20%EA%B5%AC%EC%84%B1%EB%8F%84.drawio.png" alt=""><figcaption></figcaption></figure>

&#x20;

&#x20;

<br>


# API Server 구조도

### API framework 구성 (서버 및 API 구조) <a href="#api-framework-api" id="api-framework-api"></a>

<br>

<figure><img src="/files/8fy0LRdnHtIVfopeYbqX" alt=""><figcaption></figcaption></figure>

<figure><img src="https://tech.x2bee.com/download/attachments/917517/API%20Framework%20%EA%B5%AC%EC%84%B1%202024.drawio.png" alt=""><figcaption></figcaption></figure>


# Database

X2BEE Framework는 데이터베이스 제어를 위해 Spring Boot에서 기본적으로 MyBatis를 이용하며 부가적으로 Jpa 및 QueryDsl을 이용합니다. 데이터베이스 사용을 위한 MyBatis 및 Jpa, QueryDsl 설정 방법을 설명합니다.

{% stepper %}
{% step %}

### 데이터베이스 연결, 다중 데이터베이스 연결 — 설정 (application.yml)

아래와 같이 application.yml 파일에서 서버 port번호와 데이터베이스 관련 설정을 명시합니다.

* 단일연결 (예시)

```yml
server:
  port: 8080
spring:
  config:
    activate:
      on-profile: local
  devtools:
    livereload:
      port: 3${server.port}
  datasource:
    url: jdbc:log4jdbc:postgresql://xxxxx.xxxxxx.xxx:55005/{DatabaseName}?currentSchema={SchemaName}
    driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
    username: {userName}
    password: ******************
    hikari:
      maximum-pool-size: 3
      minimum-idle: 3
      connection-timeout: 30000
      validation-timeout: 5000
      max-lifetime: 1800000
      idle-timeout: 300000
  session:
    store-type: none
  zipkin:
    enabled: false
```

* 다중연결 (예시)

```yml
server:
  port: 8097
  servlet:
    context-path: /api/bo
spring:
  config:
    activate:
      on-profile: local
  zipkin:
    enabled: false
  devtools:
    livereload:
      port: 3${server.port}
  displayrodb:
    datasource:
      url: jdbc:log4jdbc:postgresql://xxxxx.xxxxxx.xxx:55005/{DatabaseName}?currentSchema={SchemaName}
      driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      username: {userName}
      password: ******************
      hikari:
        maximum-pool-size: 5
        minimum-idle: 3
        connection-timeout: 30000
        validation-timeout: 5000
        max-lifetime: 1800000
        idle-timeout: 300000
  displayrwdb:
    datasource:
      url: jdbc:log4jdbc:postgresql://xxxxx.xxxxxx.xxx:55005/{DatabaseName}?currentSchema={SchemaName}
      driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      username: {userName}
      password: ******************
      hikari:
        maximum-pool-size: 5
        minimum-idle: 3
        connection-timeout: 30000
        validation-timeout: 5000
        max-lifetime: 1800000
        idle-timeout: 300000
```

프로퍼티 설명:

| 프로퍼티명          | 설명                   |
| -------------- | -------------------- |
| port           | tomcat 포트            |
| url            | 데이터베이스 접속 URL        |
| username       | 데이터베이스 사용자 아이디       |
| password       | 데이터베이스 사용자 비밀번호      |
| driveClassName | 데이터베이스 드라이버 클래스 명    |
| hikari         | 기타                   |
| session        | 스프링 session 설정       |
| zipkin         | MSA 환경에서 분산 트렌젝션의 추적 |
| {% endstep %}  |                      |

{% step %}

### DatabaseConfig.java 파일 작성 (예: DisplayReadWriteDatabaseConfig.java)

아래는 다중 데이터소스(읽기 전용/읽기-쓰기) 및 라우팅 데이터소스 구성 예제입니다.

```java
package com.x2bee.api.bo.base.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.x2bee.common.base.mybatis.RefreshableSqlSessionFactoryBean;
import com.x2bee.common.base.routingdatasource.RoutingDataSourceRouter;
import com.x2bee.common.base.routingdatasource.RoutingDatabase;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import lombok.extern.slf4j.Slf4j;

/**
 * display Read, Write DB 설정
 */
@Configuration
@MapperScan(value = {"com.x2bee.api.bo.app.repository.displayrodb", "com.x2bee.api.bo.app.repository.displayrwdb"}, sqlSessionFactoryRef="displayRwdbSqlSessionFactory")
@EnableJpaRepositories(
  basePackages = {"com.x2bee.api.bo.app.repository.displayrodb", "com.x2bee.api.bo.app.repository.displayrwdb"},
  entityManagerFactoryRef = "displayRwdbEntityManagerFactory",
  transactionManagerRef = "displayRwdbTxManager"
)
/* -----------------DataSource 설정------------------------------------- */

@Bean(name = "displayRodbDataSourceProperties")
@ConfigurationProperties("spring.datasource.displayrodb")
public DataSourceProperties displayRodbDataSourceProperties() {
  return new DataSourceProperties();
}

@Bean(name = "displayRwdbDataSourceProperties")
@ConfigurationProperties("spring.datasource.displayrwdb")
public DataSourceProperties displayRwdbDataSourceProperties() {
  return new DataSourceProperties();
}

@Bean(name = "displayRodbDataSource")
@ConfigurationProperties("spring.datasource.displayrodb.hikari")
public HikariDataSource displayRodbDataSource(@Qualifier("displayRodbDataSourceProperties") DataSourceProperties displayRodbDataSourceProperties) {
  HikariDataSource rodbDataSource = displayRodbDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
  rodbDataSource.setReadOnly(true);
  return rodbDataSource;
}

@Bean(name = "displayRwdbDataSource")
@ConfigurationProperties("spring.datasource.displayrwdb.hikari")
public HikariDataSource displayRwdbDataSource(@Qualifier("displayRwdbDataSourceProperties") DataSourceProperties displayRwdbDataSourceProperties) {
  return displayRwdbDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}

@Primary
@Bean(name = "displayRouteDataSource")
@ConditionalOnBean(name = {"displayRodbDataSource", "displayRwdbDataSource"})
public DataSource displayRouteDataSource(@Qualifier("displayRodbDataSource") DataSource displayRodbDataSource, @Qualifier("displayRwdbDataSource") DataSource displayRwdbDataSource) {
  Map<Object, Object> targetDataSources = new HashMap<>();
  targetDataSources.put(RoutingDatabase.READONLY, displayRodbDataSource);
  targetDataSources.put(RoutingDatabase.READWRITE, displayRwdbDataSource);
  RoutingDataSourceRouter clientRoutingDatasource = new RoutingDataSourceRouter();
  clientRoutingDatasource.setTargetDataSources(targetDataSources);
  clientRoutingDatasource.setDefaultTargetDataSource(displayRwdbDataSource);
  return clientRoutingDatasource;
}
```

(위 예제는 일부 생략된 부분(...)이 있을 수 있습니다. 실제 파일에는 SqlSessionFactory, EntityManagerFactory, TransactionManager, Querydsl 설정 등 추가 Bean 정의가 포함됩니다.)
{% endstep %}

{% step %}

### MyBatis 설정 파일 (mybatis-config.xml)

* 암호화 관련 java 파일 연결, XSS 방지처리 interceptor 설정 예:

```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>
    <setting name="cacheEnabled" value="false" />
    <setting name="mapUnderscoreToCamelCase" value="true" />
    <setting name="defaultStatementTimeout" value="30" />
  </settings>

  <plugins>
    <plugin interceptor="com.x2bee.common.base.encrypt.MybatisEncryptInterceptor"/>
    <plugin interceptor="com.x2bee.common.base.xss.XssInterceptor"/>
  </plugins>
</configuration>
```

{% endstep %}

{% step %}

### 데이터베이스 암호화 및 XssSanitizer 처리 Java 파일

(※ 데이터 마스킹 처리는 기존에 Mybatis Interceptor에서 처리하였으나 MessageConverter 응답값 반환 처리 모듈로 변경되었습니다.) (참고: Masking처리 — <https://x2bee-tech.atlassian.net/wiki/spaces/TG/pages/91127937/Masking>)

* MybatisEncryptInterceptor.java

```java
package com.x2bee.common.base.encrypt;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import com.x2bee.common.base.util.CryptoUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * Mybatis 암호화, 복호화 Interceptor
 * Encrypt Custom 어노테이션가 있는 필드에만 작동
 */
@Slf4j
@Intercepts({
  @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
  @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class })
})
public class MybatisEncryptInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    String method = invocation.getMethod().getName();
    if ("update".equals(method)) {
      return processUpdate(invocation);
    } else if ("handleResultSets".equals(method)) {
      return processQuery(invocation);
    } else {
      return invocation.proceed();
    }
  }

  private Object processUpdate(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
    Object[] args = invocation.getArgs();
    Object param = args[1];
    if (param != null) {
      Field[] fields = param.getClass().getDeclaredFields();
      for (Field field : fields) {
        Encrypt annotation = field.getAnnotation(Encrypt.class);
        if(annotation!=null && field.getType() == String.class) {
          try {
            String data = BeanUtils.getProperty(param, field.getName());
            String value = CryptoUtil.getInstance().encodeAes(data);
            BeanUtils.setProperty(param, field.getName(), value);
          } catch (Exception e) {
            log.warn(e.getMessage(), e);
          }
        }
      }
    }
    return invocation.proceed();
  }

  private Object processQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
    Object result = invocation.proceed();
    if (Objects.isNull(result)){
      return null;
    }
    if (result instanceof ArrayList) {
      ArrayList<?> resultList = (ArrayList<?>) result;
      for (int i = 0; i < resultList.size(); i++) {
        if (resultList.get(i) == null) {
          continue; // null 오류방어
        }
        Field[] fields = resultList.get(i).getClass().getDeclaredFields();
        for (Field field : fields) {
          Encrypt annotation = field.getAnnotation(Encrypt.class);
          if(annotation!=null && field.getType() == String.class) {
            try {
              String data = BeanUtils.getProperty(resultList.get(i), field.getName());
              String val = CryptoUtil.getInstance().decodeAes(data);
              BeanUtils.setProperty(resultList.get(i), field.getName(), val);
            } catch (Exception e) {
              log.warn("", e);
            }
          }
        }
      }
    } else {
      Field[] fields = result.getClass().getDeclaredFields();
      for (Field field : fields) {
        Encrypt annotation = field.getAnnotation(Encrypt.class);
        if(annotation!=null && field.getType() == String.class) {
          try {
            String val = CryptoUtil.getInstance().decodeAes(BeanUtils.getProperty(result, field.getName())+"");
            BeanUtils.setProperty(result, field.getName(), val);
          }catch (Exception e) {
            log.warn("", e);
          }
        }
      }
    }
    return result;
  }
}
```

* XssInterceptor.java

```java
package com.x2bee.common.base.xss;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Statement;
import java.util.ArrayList;

/**
 * Xss Filtering Interceptor
 * @XssSanitizer Custom 어노테이션가 있는 필드에만 작동
 */
@Slf4j
@Intercepts({
  @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
  @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class XssInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    String method = invocation.getMethod().getName();
    if ("update".equals(method)) {
      // request
      return processUpdate(invocation);
    } else {
      return invocation.proceed();
    }
  }

  private Object processUpdate(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
    Object[] args = invocation.getArgs();
    Object param = args[1];
    if (!ObjectUtils.isEmpty(param)) {
      Field[] fields = param.getClass().getDeclaredFields();
      for (Field field : fields) {
        XssSanitizer annotation = field.getAnnotation(XssSanitizer.class);
        if (annotation!=null && field.getType() == String.class) {
          try {
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            Assert.notNull(requestAttributes, "Could not find current request via RequestAttributes");
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            Assert.notNull(attributes, "Could not find current request via HttpServletRequest");
            HttpServletRequest request = attributes.getRequest();
            String requestUri = request.getRequestURI();
            String data = BeanUtils.getProperty(param, field.getName());
            String value = XssProtectUtils.getInstance().htmlRequestSanitize(data, requestUri);
            BeanUtils.setProperty(param, field.getName(), value);
          } catch (Exception e) {
            log.warn(e.getMessage(), e);
          }
        }
      }
    }
    return invocation.proceed();
  }
}
```

{% endstep %}
{% endstepper %}


# Data Validation

X2BEE Framework에서 데이터 유효성 체크 방법을 설명합니다.

Bean Validation과 Custom Validator를 사용하여 유효성 체크를 구현하는 방법으로 데이터 통신에 사용되는 VO 개체나 특정 필드에 유효성 검증을 수행하는데 활용할 수 있습니다.

***

## 유효성 체크 방법

### Bean Validation

Alias 어노테이션으로 별칭 지정합니다. 데이터 통신을 VO로 함으로, jakarta.validation.constraints 패키지에서 제공하는 어노테이션을 VO 객체에 이용합니다.

{% code title="Group.java" %}

```java
package com.x2bee.api.bo.app.entity;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.apache.ibatis.type.Alias;
import com.x2bee.common.base.entity.AbstractEntity;
import lombok.Getter;
import lombok.Setter;

@Alias("group")
@Getter
@Setter
public class Group extends AbstractEntity {
    @NotNull
    String groupNo;

    @NotEmpty
    String groupName;
}
```

{% endcode %}

### @Valid 를 이용한 @RequestBody 에 대한 유효성 검증

{% code title="SampleController.java (excerpt)" %}

```java
public class SampleController {
    ...

    @PostMapping("")
    public Response<String> registerSample(@RequestBody @Valid Sample sample) throws InterruptedException {
        ...
    }

    ...
}
```

{% endcode %}

### @Validated 를 이용한 @PathVariable과 @RequestParam 에 대한 유효성 검증

{% code title="SampleController.java (excerpt)" %}

```java
@RestController
@Validated
public class SampleController {
    @GetMapping("/users/{email}")
    public String getUserInfoByEmail(@PathVariable("email") @Email String email) {
        ....
    }
}
```

{% endcode %}

## Custom Validator

jakarta.validation.constraints 패키지에서 제공되지 않는 validator를 구현하고자 하는 경우, 아래 방법으로 진행합니다.

### Custom annotation 생성

{% code title="LocaleConstraint.java" %}

```java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = LocaleValidator.class)
@Documented
public @interface LocaleConstraint {
    String message() default "Invalid Locale";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
```

{% endcode %}

### Custom annotation을 처리할 Custom Validator 생성

{% code title="LocaleValidator.java" %}

```java
public class LocaleValidator implements ConstraintValidator<LocaleConstraint, String> {
    public static final List<String> locales = Arrays.asList("ko_KR", "en_US");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && locales.contains(value.toLowerCase());
    }
}
```

{% endcode %}

### Custom annotation @LocaleConstraint을 적용하여 유효성 검증

{% code title="Category.java" %}

```java
package com.plateer.x2co.api.prototype.entity;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;

@Data
public class Category {
    @NotEmpty
    private String catTpCd;

    @NotBlank
    private String siteNo;

    @NotEmpty
    private String dpmlNo;

    @NotEmpty
    @LocaleConstraint // 다음과 같이 어노테이션으로 적용
    private String dbLocaleLanguage;

    @NotEmpty
    private int maxLvl = 0;

    @NotEmpty
    private int minLvl = 0;
}
```

{% endcode %}


# Exception Handling

본 문서에서는 X2BEE Framework 에서의 예외 처리 방법과 관련된 내용을 기술합니다.

{% stepper %}
{% step %}

### 에러메시지 및 예외 처리

#### 에러 메시지 처리

API 오류는 공통 베이스 클래스 GlobalControllerAdvice 에 정의되며, 각 서비스의 @RestControllerAdvice 클래스(예: ApiControllerAdvice, BoControllerAdvice, DisplayControllerAdvice)가 이를 상속해 적용됩니다. 오류 내용은 Response 객체(code · message · isProcess · payload · error · errors)에 담아 반환합니다.

{% code title="GlobalControllerAdvice.java" %}

```java
package com.x2bee.common.base.exception;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Optional;

import com.x2bee.common.base.rest.HttpException;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.boot.json.JsonParseException;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.reactive.function.client.WebClientRequestException;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.servlet.NoHandlerFoundException;

import com.x2bee.common.base.context.ConfigProperties;
import com.x2bee.common.base.filter.RequestLoggingFilter;
import com.x2bee.common.base.rest.Response;
import com.x2bee.common.base.rest.ValidationError;
import com.x2bee.common.base.util.RequestUtils;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import jakarta.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class GlobalControllerAdvice {
    private String attributeError = "error";

    @ExceptionHandler(HttpInterfaceResponseException.class)
    protected ResponseEntity<Object> handleHttpInterfaceException(HttpInterfaceResponseException e, WebRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        String code = Optional.ofNullable(e.getErrorCode()).orElse(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
        String message = e.getErrorMessage();
        HttpStatus httpStatus = HttpStatus.resolve(Integer.valueOf(code));
        if (httpStatus == null) {
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
        }
        // HttpInterfaceResponseException의 경우 message값에 json 데이터가 넘어오기 때문에
        // ErrorCode로 반환하지 않고 Object형태로 그대로 반환함.
        return new ResponseEntity<>(message, httpStatus);
    }

    @ExceptionHandler(HttpException.class)
    protected ResponseEntity<Object> handleHttpException(HttpException e, WebRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        String code = Optional.ofNullable(e.getErrorCode()).orElse(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
        String message = e.getErrorMessage();
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(WebClientResponseException.class)
    protected ResponseEntity<Object> handleWebClientResponseException(WebClientResponseException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.error("", e);
        String code = Optional.ofNullable(String.valueOf(e.getStatusCode().value())).orElse(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
        String message = e.getMessage();
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(WebClientRequestException.class)
    protected ResponseEntity<Object> handleWebClientRequestException(WebClientRequestException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.error("", e);
        String code = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        String message = e.getMessage();
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(AsyncRequestTimeoutException.class)
    protected ResponseEntity<Object> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.error("", e);
        String code = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        String message = e.getMessage();
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Object> handleException(Exception e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.error("", e);
        String code = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        String message = e.getMessage();
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(value = { NullPointerException.class, IOException.class, ArrayIndexOutOfBoundsException.class, EntityNotFoundException.class, StringIndexOutOfBoundsException.class, IndexOutOfBoundsException.class, UnsupportedEncodingException.class })
    protected ResponseEntity<Object> handleErrorException(Exception e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.error("handleErrorException {}", e);
        String code = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        String message = e.getMessage();
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class, ConstraintViolationException.class, JsonParseException.class, com.fasterxml.jackson.core.JsonParseException.class, HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class, MissingServletRequestParameterException.class, MultipartException.class })
    protected ResponseEntity<Object> handleIllegalException(Exception e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.error("handleIllegalException {}", e);
        String code = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        String message = e.getMessage();
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(value = { DataAccessException.class, BadSqlGrammarException.class })
    protected ResponseEntity<Object> handleSqlException(Exception e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.error("", e);
        String code = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        String message = MessageResolver.getMessage(CommonAppError.SYSTEM_FAIL);
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(ValidationException.class)
    protected ResponseEntity<Object> handleValidationException(ValidationException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn(e.getMessage(), e);
        String code = CommonAppError.VALIDATION_EXCEPTION.getCode();
        String message = e.getMessage();
        String httpStatusCode = String.valueOf(HttpStatus.BAD_REQUEST.value());
        int httpStatus = getBadRequestHttpStatusCode(httpStatusCode);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(BindException.class)
    protected ResponseEntity<Object> handleBindException(BindException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("", e);
        String code = CommonAppError.BINDING_ERROR.getCode();
        String message = getBindingErrorMessage(e.getBindingResult());
        String httpStatusCode = String.valueOf(HttpStatus.BAD_REQUEST.value());
        int httpStatus = getBadRequestHttpStatusCode(httpStatusCode);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(e, errorCode);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("", e);
        String code = CommonAppError.BINDING_ERROR.getCode();
        String message = getBindingErrorMessage(e.getBindingResult());
        String httpStatusCode = String.valueOf(HttpStatus.BAD_REQUEST.value());
        int httpStatus = getBadRequestHttpStatusCode(httpStatusCode);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(e, errorCode);
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    protected ResponseEntity<Object> handleNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("", e);
        String code = String.valueOf(HttpStatus.METHOD_NOT_ALLOWED.value());
        String message = e.getMessage();
        int httpStatus = getBadRequestHttpStatusCode(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("", e);
        String code = String.valueOf(HttpStatus.NOT_FOUND.value());
        String message = e.getMessage();
        int httpStatus = getBadRequestHttpStatusCode(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    protected ResponseEntity<Object> handleMaxSizeException(MaxUploadSizeExceededException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("", e);
        String code = CommonAppError.INVALID_FILE.getCode();
        String message = e.getMessage();
        String httpStatusCode = String.valueOf(HttpStatus.PAYLOAD_TOO_LARGE.value());
        int httpStatus = getBadRequestHttpStatusCode(httpStatusCode);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(AuthenticationException.class)
    protected ResponseEntity<Object> handleAuthenticationException(AuthenticationException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("AuthenticationException: {}", e.getMessage());
        log.warn("", e);
        String code = String.valueOf(HttpStatus.UNAUTHORIZED.value());
        String message = e.getMessage();
        int httpStatus = getBadRequestHttpStatusCode(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(JwtException.class)
    protected ResponseEntity<Object> handleJwtException(JwtException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("JwtException: {}", e.getMessage());
        log.warn("", e);
        String code = String.valueOf(HttpStatus.UNAUTHORIZED.value());
        String message = e.getMessage();
        int httpStatus = getBadRequestHttpStatusCode(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(AccessDeniedException.class)
    protected ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("AccessDeniedException: {}", e.getMessage());
        log.warn("", e);
        String code = String.valueOf(HttpStatus.FORBIDDEN.value());
        String message = e.getMessage();
        int httpStatus = getBadRequestHttpStatusCode(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(ExpiredJwtException.class)
    protected ResponseEntity<Object> handleExpiredJwtException(ExpiredJwtException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("ExpiredJwtException: {}", e.getMessage());
        log.warn("", e);
        String code = String.valueOf(HttpStatus.FORBIDDEN.value());
        String message = e.getMessage();
        int httpStatus = getBadRequestHttpStatusCode(code);
        ErrorCode errorCode = ErrorCode.builder()
                .code(code)
                .message(message)
                .httpStatus(httpStatus)
                .build();
        return handleExceptionInternal(errorCode);
    }

    private String getBindingErrorMessage(BindingResult bindingResult) {
        if (bindingResult.hasFieldErrors()) {
            List<FieldError> errors = bindingResult.getFieldErrors();
            if (CollectionUtils.isNotEmpty(errors)) {
                FieldError error = errors.get(0);
                return error.getDefaultMessage();
            } else {
                return MessageResolver.getMessage(CommonAppError.BINDING_ERROR);
            }
        } else {
            return MessageResolver.getMessage(CommonAppError.BINDING_ERROR);
        }
    }

    private ResponseEntity<Object> handleExceptionInternal(ErrorCode errorCode) {
        ResponseEntity<Object> body = ResponseEntity.status(errorCode.getHttpStatus())
                .body(makeErrorResponse(errorCode));
        return body;
    }

    private Response<Object> makeErrorResponse(ErrorCode errorCode) {
        return Response.builder()
                .code(errorCode.getCode())
                .message(errorCode.getMessage())
                .error(true)
                .build();
    }

    private ResponseEntity<Object> handleExceptionInternal(BindException e, ErrorCode errorCode) {
        return ResponseEntity.status(errorCode.getHttpStatus())
                .body(makeErrorResponse(e, errorCode));
    }

    private Response<Object> makeErrorResponse(BindException e, ErrorCode errorCode) {
        List<ValidationError> validationErrorList = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(ValidationError::of)
                .toList();
        return Response.builder()
                .code(errorCode.getCode())
                .message(errorCode.getMessage())
                .error(true)
                .errors(validationErrorList)
                .build();
    }

    private int getBadRequestStartPrefix() {
        Integer badRequestStartPrefix = null;
        try {
            badRequestStartPrefix = ConfigProperties.getInstance().getIntValue("http-status-bad-request-start-prefix");
        } catch (Exception ex) {
            badRequestStartPrefix = null;
        }
        if (badRequestStartPrefix == null) {
            badRequestStartPrefix = 400;
        }
        return badRequestStartPrefix;
    }

    private int getBadRequestHttpStatusCode(String httpStatusCode) {
        int badRequestStartPrefix = getBadRequestStartPrefix();
        String lastCode = httpStatusCode.substring(httpStatusCode.length() - 2);
        int httpStatus = badRequestStartPrefix + Integer.valueOf(lastCode);
        return httpStatus;
    }
}
```

{% endcode %}

Response 객체에 Error 내용을 담아 Return

{% code title="Response.java" %}

```java
package com.x2bee.common.base.rest;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;

@Getter
@Setter
@NoArgsConstructor
@ToString
@Accessors(chain = true)
public class Response<T> {

    @Schema(description = "result time")
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    private LocalDateTime timestamp = LocalDateTime.now();

    @Schema(description = "result code")
    private String code = "0000";

    @Schema(description = "result message")
    private String message = "";

    @Schema(description = "process check")
    private Boolean isProcess;

    @Schema(description = "payload")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private T payload;

    @Schema(description = "is error")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Boolean error = null;

    @Schema(description = "validation error list")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<ValidationError> errors = new ArrayList<>();

    @Builder
    public Response(String code, String message, T payload, Boolean error, List<ValidationError> errors, Boolean isProcess) {
        if (code != null) {
            this.code = code;
        }
        this.message = message;
        this.payload = payload;
        this.error = error;
        this.isProcess = isProcess;
        if (errors != null && !errors.isEmpty()) {
            this.errors = errors;
        }
    }

    public Response<Object> setErrorResponse(String code, String message, Boolean isProcess) {
        return Response.builder()
                .code(code)
                .message(message)
                .isProcess(isProcess)
                .error(true)
                .build();
    }
}
```

{% endcode %}

Error Code 정의

{% code title="ApiError.java" %}

```java
package com.x2bee.api.common.base.advice;

import com.x2bee.common.base.exception.AppError;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ApiError implements AppError {
    // success
    SUCCESS("0000", "common.message.success", "common.message.success", false),

    // app error
    EMPTY_PARAMETER("1001", "common.error.emptyParameter", "common.error.emptyParameter", false),
    INVALID_PARAMETER("1002", "common.error.invalidParameter", "common.error.invalidParameter", false),
    DATA_NOT_FOUND("1003", "common.error.dataNotFound", "common.error.dataNotFound", false),
    DATA_INVALID_PARAMETER("1004", "common.error.dataInvalidParameter", "common.error.dataInvalidParameter", false),
    INVALID_FILE("1005", "common.error.invalidFile", "common.error.invalidFile", false),
    DUPLICATE_DATA("1007", "common.error.duplicateData", "common.error.duplicateData", false),
    TROUBLE_NETWORK("1008", "common.error.trouble.network", "common.error.trouble.network", false),
    TROUBLE_EXCEPTION("1009", "common.error.trouble.exception", "common.error.trouble.exception", false),
    UPLOAD_FAIL("1100", "common.error.uploadFail", "common.error.uploadFail", false),

    // ERP
    ERP_ERROR_MESSAGE("2000", "common.erp.errorMessage", "common.erp.errorMessage", false),

    // 결제관련
    PAYMENT_TOSS_NOT_INCLUDE("4001" , "paymentCommon.toss.not.include", "paymentCommon.toss.not.include", false),
    PAYMENT_CUSTOMERKEY_NOT_SAME("4002" , "paymentCommon.customerkey.not.same", "paymentCommon.customerkey.not.same", false),
    PAYMENT_REQUIED_NOT_FIND("4003" , "paymentCommon.requied.not.find", "paymentCommon.requied.not.find", false),
    PAYMENT_COMMON_IN_APRL_ERROR("4004" , "paymentCommon.in.aprl.error", "paymentCommon.in.aprl.error", false),
    PAYMENT_COMMON_CNCL_IN_APRL_SUC("4005" , "paymentCommon.cncl.in.aprl.suc", "paymentCommon.cncl.in.aprl.suc", false),
    PAYMENT_COMMON_CNCL_IN_APRL_ERROR("4006" , "paymentCommon.cncl.in.aprl.error", "paymentCommon.cncl.in.aprl.error", false),
    PAYMENT_COMMON_IN_CNCL_ERROR("4010" , "paymentCommon.in.cncl.error", "paymentCommon.in.cncl.error", false),
    PAYMENT_COMMON_IN_CNCL_SUC("4011" , "paymentCommon.in.cncl.suc", "paymentCommon.in.cncl.suc", false),
    PAYMENT_RESPONSE_EMPTY("4012" , "paymentCommon.response.empty", "paymentCommon.response.empty", false),

    // 권한 없음.
    NOT_AUTHORIZED("7000", "common.error.notAuthorized", "common.error.notAuthorized", false),

    // binding error
    BINDING_ERROR("8000", "common.error.bindingError", "common.error.bindingError", false),
    BINDING_ERROR_NOT_NULL("8001", "common.error.bindingErrorNotNull", "common.error.bindingErrorNotNull", false),

    // unknow error
    UNKNOWN("9000", getMessageUnknown(), getMessageUnknown(), false),

    // ValidatioException error
    VALIDATION_EXCEPTION("9100", getMessageUnknown(), getMessageUnknown(), false);

    private final String code;
    private final String messageKey;
    private final String boMessageKey;
    private final Boolean isProcess;

    @Override
    public boolean getIsProcess() {
        return isProcess;
    }

    private static String messageUnknown = "common.error.unknown";

    public static String getMessageUnknown() {
        return messageUnknown;
    }
}
```

{% endcode %}
{% endstep %}

{% step %}

### 예외 처리 기능 (x2bee-common 패키지 하위 Exception 클래스 구현)

* src/main/java 아래에 패키지 생성 후 Exception 코드 작성

예시 1

{% code title="CommonException.java" %}

```java
package com.x2bee.common.base.exception;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CommonException extends UserDefinedException {
    private static final long serialVersionUID = 1L;

    private final String errorCode;
    private final String errorMessage;

    public CommonException(String message, Throwable cause) {
        super(message, cause);
        this.errorCode = "500";
        this.errorMessage = message;
    }

    public CommonException(String message) {
        super(message);
        this.errorCode = "500";
        this.errorMessage = message;
    }

    public CommonException(Throwable cause) {
        super(cause);
        this.errorCode = "500";
        this.errorMessage = cause.getMessage();
    }

    public CommonException(String errorCode, String errorMessage) {
        super(errorMessage);
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }
}
```

{% endcode %}

예시 2

{% code title="ValidationException.java" %}

```java
package com.x2bee.common.base.exception;

@SuppressWarnings("serial")
public class ValidationException extends UserDefinedException {
    public ValidationException() {
        super();
    }

    public ValidationException(String message) {
        super(message);
    }

    public ValidationException(Throwable t) {
        super(t);
    }

    public ValidationException(String message, Throwable t) {
        super(message, t);
    }
}
```

{% endcode %}
{% endstep %}
{% endstepper %}


# HTTP Status Code

| Code                      | 설 명                                              |
| ------------------------- | ------------------------------------------------ |
| 200 OK                    | API 요청 성공 시 발생                                   |
| 201 Created               | 요청 성공 및 새로운 자원이 만들어진 상태 (Created)                |
| 204 No Content            | 서버가 클라이언트 요구를 처리했으나 전송할 데이터가 없는 상태 (Delete)      |
| 304 Not Modified          | 요청된 리소스를 재전송할 필요가 없는 경우 발생 (Caching)             |
| 500 Internal Server Error | 서버 에러 시 발생                                       |
| 900 Bad Request           | API 요청 실패 시 발생                                   |
| 901 Unauthorized          | 접근 권한이 없는 경우 발생 (로그인이 되어 있지 않은 경우 발생)            |
| 903 Forbidden             | 권한 밖의 일을 수행할 경우 발생 (로그인이 되어 있지만 접근 권한이 없는 경우 발생) |
| 904 Not Found             | 해당 URI와 매칭되는 리소스가 없는 경우 발생                       |
| 905 Method Not Allowed    | 지원하지 않는 메서드로 요청 시 발생                             |

{% hint style="info" %}
HTTP 응답 상태 코드는 표준 값(400·401·403·404·405·500 등)을 그대로 사용합니다. 위 표의 900번대 값은 HTTP 상태 코드가 아니라 응답 본문(Response)의 result code 값입니다. 예: 잘못된 파라미터 → HTTP 400 + code 9001·9002, 인증 필요 → HTTP 401 + 9100, 권한 없음 → HTTP 403 + 9300, 리소스 없음 → HTTP 404 + 9400.
{% endhint %}


# 로깅(logging) 설정

## 쿼리로깅 log4jdbc 설정 파일 추가

* resource 폴더 하위에 아래 파일 추가

파일: `log4jdbc.log4j2.properties`

```properties
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.dump.sql.maxlinelength=0
```

## logback 관련 설정

* `src/main/resources/{}` 폴더 하위에 `logback-spring.xml` 파일 설정
* consoleAppender: 시스템 콘솔에 찍히는 로그 정보

파일: `logback-spring.xml`

{% code title="logback-spring.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

    <springProperty scope="context" name="myappName" source="spring.application.name"/>
    <property name="MSG_FORMAT" value="%d{yyyy-MM-dd HH:mm:ss} [${myappName}] [%-5p] [%t] [%X{traceId},%X{spanId}] [%F::%M] [%line] [%X{requestURL}] : %m%n"/>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${MSG_FORMAT}</pattern>
        </layout>
    </appender>

    <property name="COLOR_MSG_FORMAT" value="%clr(%d{yyyy-MM-dd HH:mm:ss}){faint} %clr([%-5p]) [%X{requestURL}] %clr([%X{traceId},%X{spanId}]){magenta} %clr([%30.-30F::%-20.20M\\(%4L\\)]){cyan} %clr(:){faint} %m%n"/>

    <appender name="COLOR_STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${COLOR_MSG_FORMAT}</pattern>
        </layout>
    </appender>

    <springProfile name="local, dev">
        <!-- log4jdbc 옵션 설정 -->
        <logger name="jdbc" level="off"/>
        <!-- 커넥션 open close 이벤트 로그로 남김 -->
        <logger name="jdbc.connection" level="off"/>
        <!-- SQL문만을 로그로 남기며, PreparedStatement일 경우 관련된 argument 값으로 대체된 SQL문이 보여짐 -->
        <logger name="jdbc.sqlonly" level="off"/>
        <!-- SQL문과 해당 SQL을 실행시키는데 수행된 시간 정보(milliseconds)를 포함 -->
        <logger name="jdbc.sqltiming" level="debug"/>
        <!-- ResultSet을 제외한 모든 JDBC 호출 정보를 로그로 남김. 방대한 양의 로그가 생성되므로 특별히 JDBC 문제를 추적해야 할 필요가 있는 경우를 제외하고는 사용을 권장하지 않음-->
        <logger name="jdbc.audit" level="off"/>
        <!-- ResultSet을 포함한 모든 JDBC 호출 정보를 로그로 남기므로 방대한 양의 로그가 생성됨 -->
        <logger name="jdbc.resultset" level="off"/>
        <!-- SQL 결과 조회된 데이터의 table을 로그로 남김 -->
        <logger name="jdbc.resultsettable" level="debug"/>

        <logger name="com.amazonaws" level="error"/>

        <logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager" additivity="false" level="off">
            <appender-ref ref="COLOR_STDOUT" />
        </logger>

        <logger name="com.zaxxer.hikari.HikariConfig" additivity="false" level="debug">
            <appender-ref ref="COLOR_STDOUT" />
        </logger>

        <logger name="com.zaxxer.hikari" additivity="false" level="trace">
            <appender-ref ref="COLOR_STDOUT" />
        </logger>

        <logger name="com.x2bee.common" additivity="false" level="debug">
            <appender-ref ref="COLOR_STDOUT" />
        </logger>

        <logger name="com.x2bee.api" additivity="false" level="debug">
            <appender-ref ref="COLOR_STDOUT" />
        </logger>

        <root level="info">
            <appender-ref ref="COLOR_STDOUT" />
        </root>
    </springProfile>

    <springProfile name="dev, stg, qa, prd">
        <appender name="logbackTcp" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
            <destination>10.0.2.151:24224</destination>
            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <timestamp/>
                    <mdc />
                    <pattern>
                        <pattern> { "project": "${myappName}" } </pattern>
                    </pattern>
                    <logLevel/>
                    <context />
                    <threadName/>
                    <loggerName/>
                    <callerData/>
                    <message/>
                    <stackTrace/>
                </providers>
            </encoder>
        </appender>
    </springProfile>

    <springProfile name="dev, stg, qa">
        <logger name="com.zaxxer.hikari.HikariConfig" additivity="false" level="debug">
            <appender-ref ref="logbackTcp" />
        </logger>
        <logger name="com.zaxxer.hikari" additivity="false" level="debug">
            <appender-ref ref="logbackTcp" />
        </logger>
        <logger name="com.x2bee.common" additivity="false" level="debug">
            <appender-ref ref="logbackTcp" />
        </logger>
        <logger name="com.x2bee.api" additivity="false" level="debug">
            <appender-ref ref="logbackTcp" />
        </logger>
        <root level="info">
            <appender-ref ref="logbackTcp" />
        </root>
    </springProfile>

    <springProfile name="prd">
        <logger name="com.x2bee.common" additivity="false" level="warn">
            <appender-ref ref="logbackTcp" />
        </logger>
        <logger name="com.x2bee.api" additivity="false" level="warn">
            <appender-ref ref="logbackTcp" />
        </logger>
        <root level="info">
            <appender-ref ref="logbackTcp" />
        </root>
    </springProfile>
</configuration>
```

{% endcode %}

(참고) log4jdbc 관련 logger 설정 설명

* jdbc: 전체 log4jdbc 출력 옵션 (off, debug 등)
* jdbc.connection: 커넥션 open/close 이벤트 로그
* jdbc.sqlonly: SQL문만 로그 (PreparedStatement의 경우 인자가 치환된 SQL 출력)
* jdbc.sqltiming: SQL문 + 실행시간(milliseconds)
* jdbc.audit: ResultSet을 제외한 모든 JDBC 호출 로그 (대량 로그 발생)
* jdbc.resultset: 모든 JDBC 호출 로그 포함 (대량 로그 발생)
* jdbc.resultsettable: SQL 결과 조회된 데이터의 테이블 로그

릴리스/프로파일별 동작 요약

* local, dev: 콘솔 출력(COLOR\_STDOUT) 중심, 개발 디버그 로그 활성화
* dev, stg, qa: logstash TCP(appender: logbackTcp) 로 전송
* prd: logstash 전송, com.x2bee.\* 로그 레벨을 warn으로 제한


# Query Logging

본 문서에서는 log4jdbc 를 이용한 쿼리 로깅 방법에 대하여 기술합니다.

## log4jdbc 라이브러리 추가

* pom.xml 파일에 log4jdbc 라이브러리 의존성을 추가합니다.

{% code title="pom.xml" %}

```xml
<dependencies>
    ....
    <dependency>
        <groupId>org.bgee.log4jdbc-log4j2</groupId>
        <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
        <version>1.16</version>
    </dependency>
</dependencies>
```

{% endcode %}

* Gradle 사용 시 build.gradle에 의존성 추가:

{% code title="build.gradle" %}

```gradle
dependencies {
    ....
    implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
}
```

{% endcode %}

## log4jdbc 설정 파일 추가

* resources 폴더 하위에 **log4jdbc.log4j2.properties** 파일명으로 설정 파일을 추가합니다.

{% code title="log4jdbc.log4j2.properties" %}

```
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.dump.sql.maxlinelength=0
```

{% endcode %}

## DB 연결 정보 수정

* driver-class-name에 다음을 추가합니다:
  * net.sf.log4jdbc.sql.jdbcapi.DriverSpy
* URL에 log4jdbc를 추가합니다.

예시 application.yml:

{% code title="application.yml" %}

```yaml
spring:
  config:
    datasource:
      testdb:
        url: dbc:log4jdbc:postgresql://xxx.xx.xx.xx:xxxx:xxxx
        driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
```

{% endcode %}

## logback 설정에 관련 로거 추가

log4jdbc 로그를 제어하기 위해 logback 설정 파일에 아래 로거들을 추가합니다. (파일명은 환경에 따라 logback.xml 또는 logback.yml을 사용)

{% code title="logback.yml / logback.xml (예시)" %}

```xml
<!-- log4jdbc 옵션 설정 -->
<logger name="jdbc" level="off"/>

<!-- 커넥션 open close 이벤트 로그로 남김 -->
<logger name="jdbc.connection" level="off"/>

<!-- SQL문만을 로그로 남기며, PreparedStatement일 경우 관련된 argument 값으로 대체된 SQL문이 보여짐 -->
<logger name="jdbc.sqlonly" level="off"/>

<!-- SQL문과 해당 SQL을 실행시키는데 수행된 시간 정보(milliseconds)를 포함 -->
<logger name="jdbc.sqltiming" level="debug"/>

<!-- ResultSet을 제외한 모든 JDBC 호출 정보를 로그로 남김. 방대한 양의 로그가 생성되므로 특별히 JDBC 문제를 추적해야 할 필요가 있는 경우를 제외하고는 사용을 권장하지 않음-->
<logger name="jdbc.audit" level="off"/>

<!-- ResultSet을 포함한 모든 JDBC 호출 정보를 로그로 남기므로 방대한 양의 로그가 생성됨 -->
<logger name="jdbc.resultset" level="off"/>

<!-- SQL 결과 조회된 데이터의 table을 로그로 남김 -->
<logger name="jdbc.resultsettable" level="debug"/>
```

{% endcode %}

(위 설정에서 level 값은 필요에 따라 조정해서 사용하세요. 일부 로거는 대량의 로그를 생성하므로 운영 환경에서는 주의가 필요합니다.)


# PostgreSQL 참고

PostgreSQL Documentation URL: <https://www.postgresql.org/docs/>

## 참고 1 : 참고 항목

* II. The SQL Language
* 8. Data Types
* VII. Internals
* 51. System Catalogs
  * 51.64. System Views
* VIII. Appendixes
  * A. PostgreSQL Error Codes

## 참고 2 : Oracle vs PostgreSQL Data Types

Data type mapping:

|                                     | **Oracle**                                            | **PostgreSQL**                              |
| ----------------------------------- | ----------------------------------------------------- | ------------------------------------------- |
| 1                                   | BFILE                                                 | Pointer to binary file, ⇐ 4G                |
| 2                                   | BINARY\_FLOAT                                         | 32-bit floating-point number                |
| 3                                   | BINARY\_DOUBLE                                        | 64-bit floating-point number                |
| 4                                   | BLOB                                                  | Binary large object, ⇐ 4G                   |
| 5                                   | CHAR(*n*), CHARACTER(*n*)                             | Fixed-length string, 1 ⇐ *n* ⇐ 2000         |
| 6                                   | [CLOB](http://www.sqlines.com/oracle/datatypes/clob)  | Character large object, ⇐ 4G                |
| 7                                   | [DATE](http://www.sqlines.com/oracle/datatypes/date)  | Date and time                               |
| 8                                   | DECIMAL(*p,s*), DEC(*p,s*)                            | Fixed-point number                          |
| 9                                   | DOUBLE PRECISION                                      | Floating-point number                       |
| 10                                  | FLOAT(*p*)                                            | Floating-point number                       |
| 11                                  | INTEGER, INT                                          | 38 digits integer                           |
| 12                                  | INTERVAL YEAR(*p*) TO MONTH                           | Date interval                               |
| 13                                  | INTERVAL DAY(*p*) TO SECOND(*s*)                      | Day and time interval                       |
| 14                                  | LONG                                                  | Character data, ⇐ 2G                        |
| 15                                  | LONG RAW                                              | Binary data, ⇐ 2G                           |
| 16                                  | NCHAR(*n*)                                            | Fixed-length UTF-8 string, 1 ⇐ *n* ⇐ 2000   |
| 17                                  | NCHAR VARYING(*n*)                                    | Varying-length UTF-8 string, 1 ⇐ *n* ⇐ 4000 |
| 18                                  | NCLOB                                                 | Variable-length Unicode string, ⇐ 4G        |
| 19                                  | NUMBER(*p*,0), NUMBER(*p*)                            | 8-bit integer, 1 <= *p* < 3                 |
| 16-bit integer, 3 <= *p* < 5        | SMALLINT                                              |                                             |
| 32-bit integer, 5 <= *p* < 9        | INT                                                   |                                             |
| 64-bit integer, 9 <= *p* < 19       | BIGINT                                                |                                             |
| Fixed-point number, 19 <= *p* <= 38 | DECIMAL(*p*)                                          |                                             |
| 20                                  | NUMBER(*p,s*)                                         | Fixed-point number, s > 0                   |
| 21                                  | NUMBER, NUMBER(\*)                                    | Floating-point number                       |
| 22                                  | NUMERIC(*p,s*)                                        | Fixed-point number                          |
| 23                                  | NVARCHAR2(*n*)                                        | Varying-length UTF-8 string, 1 ⇐ *n* ⇐ 4000 |
| 24                                  | [RAW(n)](http://www.sqlines.com/oracle/datatypes/raw) | Variable-length binary string, 1 ⇐ n ⇐ 2000 |
| 25                                  | REAL                                                  | Floating-point number                       |
| 26                                  | ROWID                                                 | Physical row address                        |
| 27                                  | SMALLINT                                              | 38 digits integer                           |
| 28                                  | TIMESTAMP(*p*)                                        | Date and time with fraction                 |
| 29                                  | TIMESTAMP(*p*) WITH TIME ZONE                         | Date and time with fraction and time zone   |
| 30                                  | UROWID(*n*)                                           | Logical row addresses, 1 ⇐ *n* ⇐ 4000       |
| 31                                  | VARCHAR(*n*)                                          | Variable-length string, 1 ⇐ *n* ⇐ 4000      |
| 32                                  | VARCHAR2(*n*)                                         | Variable-length string, 1 ⇐ *n* ⇐ 4000      |
| 33                                  | XMLTYPE                                               | XML data                                    |

출처: <https://www.tutorialdba.com/2018/04/oracle-vs-postgresql-data-types.html>

***

## 참고 3 : PostgreSQL Table/Column 조회 샘플

SQL to retrieve table/column information:

{% code title="table\_column\_query.sql" %}

```sql
select clm.table_catalog,
       clm.table_schema,
       clm.table_name as "Table Name",
       pg_catalog.obj_description(pc.oid, 'pg_class') as "Table Comments",
       clm.column_name as "Column Name",
       pg_catalog.col_description(pa.attrelid, pa.attnum) as "Column Comments",
       clm.ordinal_position as "Column Order",
       clm.data_type as "Data Type",
       clm.is_nullable as "IsNull?",
       clm.character_maximum_length as "Char Length",
       clm.character_octet_length as "Byte Length"
from information_schema.columns clm
inner join pg_catalog.pg_class pc
    on clm.table_name = pc.relname
    and pg_catalog.pg_table_is_visible(pc.oid)
    and pc.relkind in ('r', '')
inner join pg_catalog.pg_attribute pa
    on pa.attrelid = pc.oid
    and pa.attnum > 0
    and not pa.attisdropped
where 1=1
  and clm.table_catalog = 'x2commerce'
  and clm.table_schema = 'public'
order by clm.table_name, clm.ordinal_position;
```

{% endcode %}

***

##


# 아키텍처 가이드

## 개요

아키텍처 가이드는 일관되고 표준화된 설계 작업을 할 수 있도록 돕기 위해 작성하였습니다.

사용된 소프트웨어 및 솔루션을 활용하여 향후 용이한 시스템 운영이 가능하도록 각 분야별 설계 표준을 수립하여 **\[고객사명] 시스템**에 적합한 아키텍처를 설계할 수 있도록 돕습니다.

* 전체 시스템은 AWS Cloud 위에 구성, 어플리케이션은 EKS(Elastic Kubernetes Service) 안에 Pod로 구성하며 BMS(AWS RDS, PostgreSQL), Redis, OpenSearch 등 Stateful 서비스는 AWS Managed 서비스를 이용합니다.
* 서비스 어플리케이션은 Java Spring Boot로 개발된 Restful API를 기반으로 구성합니다.
* Admin 서비스(BO, CC)는 Next.js + MUI를 사용하여 Web Page로 서비스 되며, Front 서비스(FO, MO)를 구현한다. 각 서비스의 고객 세션(또는 토큰)은 Redis에 저장하여 클러스터 서비스가 가능하도록 합니다.
* 시스템은 개발계, 검증계, 운영계로 구성하며 각각은 동일한 토폴로지(Topology)를 가집니다.

{% hint style="info" %}
이 문서의 상세 내용은 X2BEE의 기술적인 세부 정보를 포함하고 있으며, 프로젝트 개발을 지원하기 위한 자세한 내용을 다루고 있습니다.
{% endhint %}

***

### 문서 구성 <a href="#undefined" id="undefined"></a>

<details>

<summary><a href="/pages/9352ec180044338ef78a782a3acb0c0ac98d8bf3">명명규칙</a> </summary>

명명규칙은 시스템의 가독성과 관리를 개선하기 위한 중요한 요소입니다. 이 섹션에서는 명명규칙에 대해 설명합니다.

</details>

<details>

<summary><a href="/pages/d6a2801208c21466c91c3f73fb8acd96c6e4d8d7">외부 도메인</a> </summary>

외부 도메인은 시스템이 구동되는 환경을 설명합니다. 이 섹션에서는 AWS 클라우드와 관련된 외부 도메인을 다룹니다.

</details>

<details>

<summary><a href="/pages/71d244b49b40b31d7f15aadc48ae6f7f345bbfb1">AWS 리소스 구성 설계</a> </summary>

AWS 리소스를 구성하는 방법과 각 리소스의 역할에 대해 설명합니다.

</details>

<details>

<summary><a href="/pages/3de620fa952624108692d3666bb48aa0a17fcc9f">서비스(Kubernetes) 구성 설계</a> </summary>

Kubernetes를 사용하여 컨테이너 오케스트레이션과 클러스터 관리를 수행하는 방법을 다룹니다.

</details>

<details>

<summary><a href="/pages/e578920353da9948669aa0a29d07d384d302d473">애플리케이션 구성 설계</a> </summary>

각 서비스의 애플리케이션 구성에 대해 설명합니다. 이 섹션에서는 Restful API, Web Application, 및 세션 관리를 다룹니다.

</details>

<details>

<summary><a href="/pages/08001ece72339ca7a05641e49009897afffb5d01">CI/CD 구성 설계</a> </summary>

지속적 통합 (CI) 및 지속적 배포 (CD) 환경을 구성하는 방법과 자동화된 빌드, 테스트, 및 배포를 다룹니다.

</details>

<details>

<summary><a href="/pages/51aed90897b674b166d7bacc0cfb5d6f2aa8e289">성능 향상/보장 방안</a> </summary>

시스템의 성능을 향상시키고 보장하기 위한 방안과 전략에 대해 설명합니다.

</details>


# 명명규칙

{% file src="/files/nNfG1WvgmmSGDG51Y5Em" %}

<br>


# 외부 도메인 소개

| 구분 | 환경  | 용도     | 도메인                                                | 프로토콜  |
| -- | --- | ------ | -------------------------------------------------- | ----- |
| 외부 |     | ROOT   | \[고객사도메인].com                                      |       |
| 외부 | DEV | FO     | x2bee-fo-dev.\[고객사도메인].com                         | HTTPS |
| 외부 | DEV | CC     | x2bee-cc-dev.\[고객사도메인].com                         | HTTPS |
| 외부 | DEV | 동영상    | x2bee-video-dev.\[고객사도메인].com                      | HTTPS |
| 외부 | DEV | 상품 이미지 | x2bee-img-dev.\[고객사도메인].com                        | HTTPS |
| 외부 | STG | FO     | x2bee-fo-stg.\[고객사도메인].com                         | HTTPS |
| 외부 | STG | CC     | x2bee-cc-stg.\[고객사도메인].com                         | HTTPS |
| 외부 | STG | 동영상    | x2bee-video-stg.\[고객사도메인].com                      | HTTPS |
| 외부 | STG | 상품 이미지 | x2bee-img-stg.\[고객사도메인].com                        | HTTPS |
| 외부 | QA  | FO     | x2bee-fo-qa.\[고객사도메인].com                          | HTTPS |
| 외부 | QA  | CC     | x2bee-cc-qa.\[고객사도메인].com                          | HTTPS |
| 외부 | QA  | 동영상    | x2bee-video-qa.\[고객사도메인].com                       | HTTPS |
| 외부 | QA  | 상품 이미지 | x2bee-img-qa.\[고객사도메인].com                         | HTTPS |
| 외부 | PRD | FO     | [www.\\\[고객사도메인\].com](http://www.\\\[고객사도메인].com) | HTTPS |
| 외부 | PRD | CC     | ccw.\[고객사도메인].com                                  | HTTPS |
| 외부 | PRD | 동영상    | video.\[고객사도메인].com                                | HTTPS |
| 외부 | PRD | 상품 이미지 | img.\[고객사도메인].com                                  | HTTPS |


# AWS 리소스 구성 설계

## DEV

<figure><img src="/files/yHbPjfF2ZcEWPhNwkrho" alt=""><figcaption></figcaption></figure>

<table><thead><tr><th width="188.4443359375">리소스 구성</th><th>정보(DEV)</th></tr></thead><tbody><tr><td><strong>S3</strong></td><td><ul><li>x2bee-stg-pri-attachment-s3 / 이미지 원본  업로드</li><li>x2bee-stg-pri-studio-s3 / 동영상 관련 변환 content</li></ul></td></tr><tr><td><strong>VPC</strong></td><td>CIDR : 10.0.0.0/16</td></tr><tr><td><strong>EKS</strong></td><td><ul><li>Kubernetes 버전 : 1.21</li><li>AWS EKS Cluster: X2BEE-EKS</li><li>NodeGroup : X2BEE-APP</li></ul></td></tr><tr><td><strong>EC2</strong></td><td><ul><li>EKS Node Group : m5.xlarge(vCPUs 4 / Memory 16GiB)</li><li>EC2-X2CO-REPO (Gitlab, Nexus) : t3.large (vCPU 2 / Memory 8GiB)</li><li>Sonarqube : t3.medium (vCPU 2 / Memory 4GiB)</li><li>Jenkins : t3a.xlarge (vCPU 4 / Memory 16GiB)</li></ul></td></tr><tr><td><strong>ElastiCache(Redis)</strong></td><td><ul><li>t3.medium (vCPU 2 / Memory 4GiB)</li></ul></td></tr><tr><td><strong>DB</strong></td><td><ul><li>Master – Read/Write : t3.xlarge(vCpu:4,Memory:16Gbi) / 쓰기/읽기</li><li>Replica - ReadOnly : t3.xlarge(vCpu:4,Memory:16Gbi) / 읽기</li></ul></td></tr><tr><td><strong>ELB(ALB)</strong></td><td><ul><li>Lambda – instance의 auto stop / start 설정</li><li>SES – 대량 이메일 전송 서비스</li></ul></td></tr></tbody></table>

### STG

<figure><img src="/files/BDVS4W73Z8U0A4hTkX5V" alt=""><figcaption></figcaption></figure>

<table><thead><tr><th width="180.6666259765625">리소스 구성</th><th>정보(STG)</th></tr></thead><tbody><tr><td><strong>S3</strong></td><td><ul><li>[고객사약어]-stg-pri-logs-s3 / AWS 관련 log</li><li>[고객사약어]-stg-pri-media-contents-s3 / 동영상 관련 원본 업로드</li><li>[고객사약어]-stg-pri-media-origin-s3 / 동영상 관련 변환 content</li><li>[고객사약어]-stg-pri-web-static-s3 / 웹 정적 자원 저장소</li></ul></td></tr><tr><td><strong>VPC</strong></td><td>CIDR : xx.xx.xx.xx/20</td></tr><tr><td><strong>EKS</strong></td><td><ul><li>Kubernetes 버전 : 1.27</li><li>[고객사약어]-stg-eks-cluster</li><li>NodeGroup : MGMT, APP</li></ul></td></tr><tr><td><strong>EC2</strong></td><td><ul><li>workbench : t3.small(vCpu:2,Memory:2GiB)</li><li>EKS Node Group : t3.medium(vCpu:2,Memory:4GiB)</li><li>ETL : 미정</li></ul></td></tr><tr><td><strong>ElastiCache(Redis)</strong></td><td>Session : cache.t3.small(단일 구성)<br>(vCpu:2,Memory:1.55GiB)</td></tr><tr><td><strong>DB</strong></td><td><ul><li>주문<br>db.t3.medium(vCpu:2,Memory:4GiB) 쓰기<br>db.t3.medium(vCpu:2,Memory:4GiB) 읽기</li><li>상품<br>db.t3.medium(vCpu:2,Memory:4GiB) 쓰기<br>db.t3.medium(vCpu:2,Memory:4GiB) 읽기</li><li>이벤트<br>db.t3.medium(vCpu:2,Memory:4GiB) 쓰기<br>db.t3.medium(vCpu:2,Memory:4GiB) 읽기</li></ul></td></tr><tr><td><strong>ELB(ALB)</strong></td><td><ul><li>External ALB : 외부 접근 용도</li><li>Internal ALB : 내부 인터페이스 용도</li></ul></td></tr></tbody></table>

### QA

<figure><img src="/files/nsr3985Rn3iKAbMyGpPz" alt=""><figcaption></figcaption></figure>

<table><thead><tr><th width="180.6666259765625">리소스 구성</th><th>정보(QA)</th></tr></thead><tbody><tr><td><strong>S3</strong></td><td><ul><li>[고객사약어]-qa-pri-logs-s3 / AWS 관련 log</li><li>[고객사약어]-qa-pri-media-contents-s3 / 동영상 관련 원본 업로드</li><li>[고객사약어]-qa-pri-media-origin-s3 / 동영상 관련 변환 content</li><li>[고객사약어]-qa-pri-web-static-s3 / 웹 정적 자원 저장소</li></ul></td></tr><tr><td><strong>VPC</strong></td><td>CIDR : xx.xx.xx.xx/20</td></tr><tr><td><strong>EKS</strong></td><td><ul><li>Kubernetes 버전 : 1.27</li><li>[고객사약어]-qa-eks-cluster</li><li>NodeGroup : MGMT, APP</li></ul></td></tr><tr><td><strong>EC2</strong></td><td><ul><li>workbench : t3.small(vCpu:2,Memory:2GiB)</li><li>EKS Node Group : t3.medium(vCpu:2,Memory:4GiB)</li><li>ETL : 미정</li></ul></td></tr><tr><td><strong>ElastiCache(Redis)</strong></td><td>Session : cache.t3.small(단일 구성)<br>(vCpu:2,Memory:1.55GiB)</td></tr><tr><td><strong>ELB(ALB)</strong></td><td><ul><li>External ALB : 외부 접근 용도</li><li>Internal ALB : 내부 인터페이스 용도</li></ul></td></tr></tbody></table>

### PRD

<figure><img src="/files/UAxeAIBaq7Cfa21pP7oF" alt=""><figcaption></figcaption></figure>

<table><thead><tr><th width="172.888916015625">리소스 구성</th><th>정보(PRD)</th></tr></thead><tbody><tr><td><strong>S3</strong></td><td><ul><li>[고객사약어]-prd-pri-logs-s3 / AWS 관련 log</li><li>[고객사약어]-prd-pri-media-contents-s3 / 동영상 관련 원본  업로드</li><li>[고객사약어]-prd-pri-media-origin-s3 / 동영상 관련 변환 content</li><li>[고객사약어]-prd-pri-web-static-s3 / 웹 정적 자원 저장소</li></ul></td></tr><tr><td><strong>VPC</strong></td><td>CIDR : xx.xx.xx.xx/16</td></tr><tr><td><strong>EKS</strong></td><td><ul><li>Kubernetes 버전 : 1.27</li><li>[고객사약어]-prd-eks-cluster</li><li>NodeGroup : MGMT, APP</li></ul></td></tr><tr><td><strong>EC2</strong></td><td><ul><li>workbench : t3.small(vCPUs 2 / Memory 2GiB)</li><li>EKS Node Group : c5.xlarge(vCPUs 4 / Memory 8GiB)</li><li>ETL : 미정</li></ul></td></tr><tr><td><strong>ElastiCache(Redis)</strong></td><td><ul><li>Active : cache.m5.xlarge / (vCPUs 4 / Memory 12.93 GiB)</li><li>Standby : cache.m5.xlarge / (vCPUs 4 / Memory 12.93 GiB)</li></ul></td></tr><tr><td><strong>DB</strong></td><td><ul><li>주문<br>db.r5.2xlarge(vCpu:8,Memory:64Gbi) / 쓰기<br>db.r5.2xlarge(vCpu:8,Memory:64Gbi) / 읽기(장애 시 전환)<br>db.r5.xlarge(vCpu:8,Memory:32Gbi) / 읽기</li><li>상품<br>db.r5.2xlarge(vCpu:8,Memory:64Gbi) / 쓰기<br>db.r5.2xlarge(vCpu:8,Memory:64Gbi) / 읽기(장애 시 전환)<br>db.r5.2xlarge(vCpu:8,Memory:64Gbi) / 읽기</li><li>이벤트<br>db.r5.xlarge(vCpu:8,Memory:32Gbi) / 쓰기<br>db.r5.xlarge(vCpu:8,Memory:32Gbi) / 읽기(장애 시 전환)<br>db.r5.xlarge(vCpu:8,Memory:32Gbi) / 읽기</li></ul></td></tr><tr><td><strong>ELB(ALB)</strong></td><td><ul><li>External ALB : 외부 접근 용도</li><li>Internal ALB : 내부 인터페이스 용도</li><li>Brand ALB : systembts, systemjeans 용도</li></ul></td></tr></tbody></table>


# 서비스(kubernetics) 구성 설계

### Application Service

<figure><img src="/files/xNGuSKWwkIyzPNNoiewn" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="268.4444580078125"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>Client / BFF / GW – BO, CC</strong></td><td><ul><li>BO와 CC는 클라이언트(PC 브라우저 + JavaScript)를 대상으로 하며, Server Side Rendering(SSR)을 지원하는 Next.js로 어플리케이션아 구성된다.</li></ul></td></tr><tr><td><strong>Client / BFF / GW – FO (PC, MO)</strong></td><td><ul><li>FO(PC, MO) Client는 Next.js로 개발되며 FO Gateway로 부터 Restful API 서비스를 제공받는다.</li><li>위 그림은 논리적 구성을 표현하며 실제적으로는 Node.js를 이용한 SSR을 한다.</li></ul></td></tr><tr><td><strong>APIs</strong></td><td><ul><li>Springboot로 구현된 Restful API 서비스</li><li>order, member, common, goods, display, event와 bo API 서비스로 구성된다.</li></ul></td></tr><tr><td><strong>DBMS</strong></td><td><ul><li>회원/주문 {order, member, common}</li><li>상품/전시{goods, display}</li><li>이벤트 {event}</li><li>Master(Read-Write)/Replica(Read Only) 구조로 이중화</li><li>ETL 혹은 배치잡을 이용한 table 동기화</li></ul></td></tr></tbody></table>

### Log Stack

<figure><img src="/files/eSDGM3GqJts1wUT9vg4E" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="145.111083984375"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>OpenSearch</strong></td><td><ul><li>수집되는 application 로그를 인덱싱하여 저장</li></ul></td></tr><tr><td><strong>Fluentd</strong></td><td><ul><li>Application의 로그를 수집하여 포매팅하여 OpenSearch에 전송</li></ul></td></tr><tr><td><strong>Kibana</strong></td><td><ul><li>OpenSearch의 로그를 개발자가 조회할 수 있는 UI 제공</li></ul></td></tr><tr><td><strong>EFK 서버 위치</strong></td><td><ul><li>fluentd : Kubernetes</li><li>OpenSearch : AWS(Managed Service)</li><li>Kibana : AWS(Managed Service)</li></ul></td></tr><tr><td><strong>로그 보관</strong></td><td><ul><li>OpenSearch와는 별개로 로그는 보관 기간에 따라 S3에 압축 보관</li></ul></td></tr></tbody></table>

### Telemetry

<figure><img src="/files/JGt78vabItxlpJ0sj09h" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="194.0001220703125"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>APM</strong></td><td><ul><li>어플리케이션 성능 관리. Request 처리 성능의 병목 구간을 찾아 개선</li><li>APM제공 라이브러리를 함께 application에 구성<br>(* 사용 제품 미정)</li></ul></td></tr><tr><td><strong>Prometheous-Grafana</strong></td><td><ul><li>Pod에 metric(cpu, memory, network utilization 등) 모니터링</li><li>Istio envoy에서 제공</li></ul></td></tr><tr><td><strong>Kiali</strong></td><td><ul><li>MSA 간 호출의 처리량, 응답속도, 정상여부 상황을 트래픽 flow로 모니터링</li><li>Istio envoy에서 제공</li></ul></td></tr><tr><td><strong>Jaeger</strong></td><td><ul><li>하나의 request 처리를 위한 MSA간의 호출, 처리 관계를 상세 분석할 수 있도록 기능 제공</li><li>Istio envoy에서 제공</li></ul></td></tr></tbody></table>

### Software 구성

<figure><img src="/files/q36rZXXsuNCDSzjvmlpO" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="178.4444580078125"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>Application</strong></td><td>Springboot</td></tr><tr><td><strong>Container</strong></td><td>Docker</td></tr><tr><td><strong>Orchestration</strong></td><td>EKS(Kubernetes)</td></tr><tr><td><strong>Servismesh</strong></td><td>istio</td></tr><tr><td><strong>CICD</strong></td><td>Gitlab, Nexus, Jenkins, Sonarqube, Helm Chart, ArgoCD</td></tr><tr><td><strong>Logging</strong></td><td>EFK (OpenSearch, Fluentd, Kibana)</td></tr><tr><td><strong>Telemetry</strong></td><td>EKS</td></tr><tr><td><strong>Batch Scheduler</strong></td><td>Cronicle</td></tr><tr><td><strong>APM</strong></td><td>WhaTap or Scouter</td></tr><tr><td><strong>DBMS</strong></td><td>PostgreSQL</td></tr><tr><td><strong>In-Memory</strong></td><td>Redis(Session Clustering)</td></tr></tbody></table>

### Software 목록

| **적용 영역**                     | **적용 솔루션**     | **라이선스**               | **버전 / 기타**               |
| ----------------------------- | -------------- | ---------------------- | ------------------------- |
| \*\* External Proxy (ALB)\*\* | AWS ALB        | AWS PaaS               | Managed Service           |
| **Servie Mesh (G/W)**         | Istio          | Apache License 2.0     | Version 1.8 이상            |
| **Orchastration**             | Kubernetes     | Apache License 2.0     | Version 1.19 이상           |
| **Data Storage**              | S3(AWS)        | AWS PaaS               | Managed Service           |
| **CI/CD**                     | Gitlab         | MIT License            | 소스 통합                     |
|                               | Jenkins        | MIT License            | CI/CD Pipeline            |
|                               | ArogCD         | Apache License 2.0     | Kubernetes Deployment     |
|                               | Helm           | Apache License 2.0     | Kubernetes Deployment     |
|                               | NexusOSS       | Eclipse Public License | Library, Docker Image 저장소 |
| **Logging**                   | Fluentd        | Apache License 2.0     | Application 로그 수집         |
|                               | OpenSearch     | AWS PaaS               | Managed Service, 로그 인덱스   |
|                               | Kibana         | AWS PaaS               | Managed Service, 로그 분석 화면 |
| **Telemetry**                 | Prometheus     | Apache License 2.0     | 서버 metric 수집              |
|                               | Grafana        | Apache License 2.0     | 서버 metric UI 제공           |
|                               | Kiali          | Apache License 2.0     | API 호출 모니터링               |
|                               | Jaeger(Zipkin) | Apache License 2.0     | API 호출 상세 분석              |
|                               | WhaTap (미정)    | 유료 라이선스                | TP Monitor (미정)           |
| **DBMS**                      | PostgreSQL     | OSI License            | Persistence DBMS          |
| **In-Memory DB**              | Redis          | AWS PaaS               | Managed Service, 세션 클러스터링 |

### Kubernetes Apps

<figure><img src="/files/z6BIU9iL42n4cCYuZNtk" alt=""><figcaption></figcaption></figure>

| **Deployment / Pod**           | **Namespace** | **Workload** | **Node Label** | **Description**                                           |
| ------------------------------ | ------------- | ------------ | -------------- | --------------------------------------------------------- |
| **x2bee-fo**                   | \[고객사약어]-app  | deployment   | msa            | Node.js Service for PC Web(Next.js Server Side Rendering) |
| **x2bee-gw**                   | \[고객사약어]-app  | deployment   | msa            | RestAPIs Service for Vue.js (Spring Cloud Gateway)        |
| **x2bee-bo**                   | \[고객사약어]-app  | deployment   | msa            | Back Office Web Service (Spring Boot, Next.js)            |
| **x2bee-cc**                   | \[고객사약어]-app  | deployment   | msa            | Customer Center Web Service (Spring Boot, Next.js)        |
| **x2bee-batch-mbod**           | \[고객사약어]-app  | deployment   | msa            | Batch service for member, order (Spring Batch)            |
| **x2bee-batch-gddp**           | \[고객사약어]-app  | deployment   | msa            | Batch service for goods, display (Spring Batch)           |
| **x2bee-api-member**           | \[고객사약어]-app  | deployment   | msa            | Member api service (Springboot, RestApis)                 |
| **x2bee-api-order**            | \[고객사약어]-app  | deployment   | msa            | Order api service (Springboot, RestApis)                  |
| **x2bee-api-goods**            | \[고객사약어]-app  | deployment   | msa            | Goods api service (Springboot, RestApis)                  |
| **x2bee-api-display**          | \[고객사약어]-app  | deployment   | msa            | Display api service (Springboot, RestApis)                |
| **x2bee-api-event**            | \[고객사약어]-app  | deployment   | msa            | Event api service (Springboot, RestApis)                  |
| **x2bee-api-common**           | \[고객사약어]-app  | deployment   | msa            | Common api service (Springboot, RestApis)                 |
| **x2bee-api-bo**               | \[고객사약어]-app  | deployment   | msa            | Bo api service (Springboot, RestApis)                     |
| **istio-ingressgateway**       | istio-system  | deployment   | msa            | Istio ingress component                                   |
| **istio-tracing**              | istio-system  | deployment   | msa            | istio tracing component                                   |
| **x2bee-batch-scheduler-mbod** | \[고객사약어]-app  | deployment   | msa            | Batch scheduler for batch-mbod (node.js)                  |
| **x2bee-batch-scheduler-gddp** | \[고객사약어]-app  | deployment   | msa            | Batch scheduler for batch-gddp (node.js)                  |
| **grafana**                    | monitoring    | deployment   | msa            | monitoring bi                                             |
| **prometheus**                 | monitoring    | deployment   | msa            | to gather pod metrics                                     |
| **kiali**                      | istio-system  | deployment   | msa            | to monitor msa calls flows                                |
| **jaeger**                     | istio-system  | deployment   | msa            | to trace each msa call chain                              |


# 어플리케이션 구성 설계

## Application 프로젝트

<figure><img src="/files/wzbLnQMpeqMQsoWvOH2U" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="191.77783203125"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>x2bee-common</strong></td><td>- 자바 프로젝트에 공통으로 사용될 framework 관련 모듈, spring boot 기본 설정으로 제공하지 못하는 기능을 제공<br>- jar 모듈</td></tr><tr><td><strong>x2bee-api</strong></td><td>- Rest API 제공 서비스<br>- Springboot, Rest API<br>- x2bee-api-member : 회원, 로그인 서비스<br>- x2bee-api-order : 주문 및 클레임 처리 서비스<br>- x2bee-api-goods : 상품 서비스<br>- x2bee-api-display : 전시 서비스<br>- x2bee-api-event : 이벤트 서비스<br>- x2bee-api-common : 사용자, 협력사, 시스템 공통 서비스</td></tr><tr><td><strong>x2bee-gw</strong></td><td>- front API Gateway, 사용자 인증 서비스<br>- Springboot, Spring Cloud Gateway</td></tr><tr><td><strong>x2bee-bo</strong></td><td>- 관리자를 위한 화면 제공 서비스<br>- Springboot, Next.js</td></tr><tr><td><strong>x2bee-cc</strong></td><td>- 고객센터를 위한 화면 제공 서비스.<br>- Springboot, Next.js</td></tr><tr><td><strong>x2bee-batch-mbod</strong></td><td>- 회원, 주문(클레임) batch 서버<br>- Springbatch</td></tr><tr><td><strong>x2bee-batch-gddp</strong></td><td>- 상품, 전시 batch 서버<br>- Springbatch</td></tr></tbody></table>

## \[고객사약어]-GW

<figure><img src="/files/nHC8mLxAotv5Hwzv3LHs" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="140.666748046875"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>Node.js</strong></td><td>- Next.js 의 Server Side Rendering 서버.</td></tr><tr><td><strong>FO Gateway Layer</strong></td><td>- Spring Cloud Gateway로 구현. Rest API Url에 따라 Predicate, Filter 기능 제공<br>- Tomcat서버가 내장되어 내부 MSA 서비스 호출</td></tr></tbody></table>

## \[고객사약어]-BO, CC

<figure><img src="/files/oWXHY2CJPepKH4JpwWrn" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="176.22216796875"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>Presentation Layer</strong></td><td>- Server Side Rendering을 지원하는 React.js 기반 Next.js를 사용하여 웹 서비스 구현<br>- HTML5/CSS3 표준을 준수</td></tr><tr><td><strong>Controller Layer</strong></td><td>- URL과 매핑된 메소드를 통해 Model과 View의 연결 수행<br>- 요청에 대한 Validation 처리및 Business Logic 수행 시의 Exception의 처리(Error 메시지 처리 Logic)</td></tr><tr><td><strong>Service Layer</strong></td><td>- 회원/로그인, 주문/클레임, 상품, 전시/검색, 시스템공통, 이벤트 등의 업무 수행 시 필요한 비즈니스를 통합한 하나의 서비스로 구성<br>- WebClient를 이용하여 MAS Service를 호출하여 비즈니스 처리를 수행</td></tr><tr><td><strong>x2bee-common</strong></td><td>- 다국어 서비스 등을 포함한 채널 관리, 접근 관리 등 사이트 전방에 적용되는 공통 기능 유틸리티</td></tr></tbody></table>

## \[고객사약어]-Api-\*

<figure><img src="/files/IgCzz7viyel3jfvDn5Q7" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="197.333251953125"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>Presentation Layer</strong></td><td>- Json Format의 Restful API Service</td></tr><tr><td><strong>Controller Layer</strong></td><td>- URL과 매핑된 메소드를 통해 Model과 View의 연결 수행<br>- 요청에 대한 Validation 처리 및 Business Logic 수행 시의 Exception의 처리(Error 메시지 처리 Logic)</td></tr><tr><td><strong>Service Layer</strong></td><td>- 각각의 회원/로그인, 주문/클레임, 상품, 전시/검색, 시스템공통, 이벤트 업무 서비스<br>- 요청된 업무를 각 비즈니스 서비스 영역별로 수행을 요청하여 처리한 후 처리 결과를 Controller에 반환</td></tr><tr><td><strong>Data Access Layer</strong></td><td>- 서비스 오브젝트와 DBMS 간의 매핑을 담당하는 영역<br>- Persistence 처리 로직 구현<br>- 각각 용도에 따라 ReadWrite 또는 ReadOnly 커넥션 사용</td></tr><tr><td><strong>x2bee-common</strong></td><td>- 다국어 서비스 등을 포함한 채널 관리, 접근 관리 등 사이트 전방에 적용되는 공통 기능 유틸리티</td></tr></tbody></table>

###


# CI/CD 구성 설계

## Kubernetes App

<figure><img src="/files/MMoZHNNtgGUs91asagXo" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="215.111083984375"></th><th></th></tr></thead><tbody><tr><td><strong>리소스 구성</strong></td><td><strong>정보</strong></td></tr><tr><td><strong>소스/스크립트 커밋</strong></td><td>개발자는 개발 완료된 소스를 Gitlab 저장소에 commit</td></tr><tr><td><strong>소스 및 스크립트 참조</strong></td><td>Jenkins는 개발 완료된 소스를 Gitlab으로부터 pull</td></tr><tr><td><strong>라이브러리 참조</strong></td><td>maven 빌드 진행 중 필요한 라이브러리는 nexus에서 참조</td></tr><tr><td><strong>컨테이너 이미지 저장</strong></td><td>빌드 완료되면 docker 이미지를 생성해서 Nexus 저장소에 이미지를 저장</td></tr><tr><td><strong>배포 요청</strong></td><td>ArgoCD에 배포 요청</td></tr><tr><td><strong>스크립트 참조</strong></td><td>ArgoCD는 배포 스크립트를 Gitlab에서 pull 하고 스트립트 내용을 Kubernetes에 전달</td></tr><tr><td><strong>컨테이너 동기화 요청</strong></td><td>개발자는 개발 완료된 소스를 Gitlab 저장소에 commit</td></tr><tr><td><strong>스케줄링</strong></td><td>해당 Deploy를 수행할 Worker Node 지정</td></tr><tr><td><strong>어플리케이션 이미지 참조</strong></td><td>Nexus에 저장된 docker 이미지를 받아서 Deploy</td></tr><tr><td><strong>정적 콘텐츠 배포</strong></td><td>어플리케이션에 포함된 정적 콘텐츠는 S3로 배포</td></tr></tbody></table>

## CI / CD 환경

<figure><img src="/files/xrFz7kjt6wcKiDlAuiiY" alt=""><figcaption></figcaption></figure>

{% stepper %}
{% step %}

### 개발 / Push

개발팀이 IDE(STS4)에서 Source Coding을 한 이후 Gitlab 서버에 Push

* Source Coding
* 단위 테스트(JUnit4) 작성 및 수행
* Code Inspection(CheckStyle, PMD, FindBugs) 수행
  {% endstep %}

{% step %}

### 빌드 트리거 설정 및 실행

운영팀이 환경이나 상태를 고려하여 Jenkins의 Build trigger 조건 설정 및 실행

* 수동 실행 / 주기적 실행 / 다른 프로젝트 성공 시에 실행 / Gitlab Push Event를 감지하여 실행
  {% endstep %}

{% step %}

### Inspection — CheckStyle

코딩 포맷 검사 수행 (실패 시 다음 단계 진행 가능 여부는 설정에 따름)

* JAVA 기본 문법에 부합하는지 검사
* 프로젝트의 Coding Style Guide를 준수하고 있는지 검사
  {% endstep %}

{% step %}

### Inspection — PMD

불필요한 코드에 대한 검사 수행 (실패 시 다음 단계 진행 가능 여부는 설정에 따름)

* 사용되지 않는 변수나 함수에 대한 검사
  {% endstep %}

{% step %}

### 단위 테스트

단위 테스트 수행 (단위 테스트가 성공한 경우에만 다음 단계로 진행 가능)

* JUnit 테스트 수행
* 단위 테스트를 성공한 경우 Code Coverage 정보를 생성
  {% endstep %}

{% step %}

### Maven Build 및 아티팩트 저장

Maven Build를 통한 Spring boot jar를 생성하고 Nexus에 저장
{% endstep %}

{% step %}

### Inspection — FindBugs

오류 발생 가능한 부분에 대한 검사 수행 (실패 시 다음 단계 진행 가능 여부는 설정에 따름)

* 런타임에서 오류가 발생될 가능성이 있는 부분에 대한 검사 (ex: NullPointer)
  {% endstep %}
  {% endstepper %}

###


# 성능 향상/보장 방안

## 전시, 상품 <a href="#undefined" id="undefined"></a>

<figure><img src="https://tech.x2bee.com/download/attachments/2654357/%EC%A0%84%EC%8B%9C_%EC%83%81%ED%92%88.png" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="166.22216796875"></th><th></th></tr></thead><tbody><tr><td>리소스 구성</td><td>내용</td></tr><tr><td><p> </p><p> </p><p>요약 테이블</p></td><td><p>프런트에서 빈번하게 조회하는 데이터가 Full Scan, Sort 등을 유발하는 테이블 구조</p><p>반정규화로 해소가 되지 않는 상품, 전시, 프로모션 가격 테이블에 대하여 요약 테이블을 만들어 Index Range Scan으로 조회할 수 있음</p><p> </p><ul><li>관리자에서 수정, 프런트에서 조회</li><li>요약 테이블 생성, 전파 후 데이터 조회 가능</li><li>요약 테이블 생성 배치</li></ul></td></tr><tr><td><p> </p><p> </p><p> </p><p>Master-Replica</p></td><td><p>AWS RDS 제공(또는 PostgreSQL 기술 이용하여 구성) Master의 데이터를 CDC 이용하여 Replica로 복제 동기화함</p><p> </p><ul><li>Master와 Replica는 동일한 데이터</li><li>1개의 Master</li><li>Replica는 Scale Out 가능</li><li>Master와 Replica는 동일한 데이터</li><li>Master 장애 시 Replica 중 1개가 Master 역할 대체</li></ul></td></tr></tbody></table>

&#x20;

## Application <a href="#application" id="application"></a>

<figure><img src="https://tech.x2bee.com/download/attachments/2654357/%EC%84%B1%EB%8A%A5%ED%96%A5%EC%83%81_%EB%B3%B4%EC%9E%A5%EB%B0%A9%EC%95%88_Application.png" alt=""><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="141.7777099609375"></th><th></th></tr></thead><tbody><tr><td>리소스 구성</td><td>내용</td></tr><tr><td><p> </p><p> </p><p> </p><p>Spring Cache</p></td><td><p>&#x3C;Method +Parameter> 를 키값으로 호출 응답 객체를 캐시 함</p><ul><li><p>사용가능 저장소</p><ul><li>EhCache (Heap Memoty)</li><li>Redis</li><li>Caffeine</li><li>Hazelcate</li><li>Infinispan</li></ul></li></ul></td></tr><tr><td><p> </p><p>EHCache </p></td><td><ul><li>별도의 서버 구성 없이 사용 가능</li><li>캐시 Hit 시 속도가 빠름</li><li>개별 VM마다 캐시 필요(Hit율 하락)</li><li>캐시 메모리가 상대적으로 작음</li></ul></td></tr><tr><td><p> </p><p>Redis</p></td><td><ul><li>한 번 캐시하면 복수의 Pod Replica가 같이 사용(Hit 율 증가)</li><li>대용량으로 캐시 메모리 사용 가능</li><li>캐시 Hit 시 조회 속도가 상대적으로 느림</li><li>별도로 Redis 서버 필요(비용 증가)    </li></ul></td></tr></tbody></table>

## CDN <a href="#cdn" id="cdn"></a>

<figure><img src="https://tech.x2bee.com/download/attachments/2654357/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98_CDN.png" alt="" width="563"><figcaption></figcaption></figure>

<table data-header-hidden><thead><tr><th width="134"></th><th></th></tr></thead><tbody><tr><td>리소스 구성</td><td>내용</td></tr><tr><td><p> </p><p>CDN</p></td><td><p>Contents Delivery Network동일한 요청 url에 대하여 동일한 내용을 리턴하는 정적 컨텐츠의 경우 매번 파일시스템 (S3)까지 요청이 와서 처리하는 것이 아니라 네트워크 앞단에 클러스터로 구성된 CDN을 이용하여 사용자에게 빠른 delivery를 하여 클라이언트 속도를 향상한다.</p><p>– 이용 CDN : CloudFront</p></td></tr><tr><td>Purge</td><td>대량의 컨텐츠 변경 또는 긴급 변경 시 캐시된 컨텐츠를 즉시 변경하는 기능</td></tr></tbody></table>

&#x20;

## Front - End <a href="#front-end" id="front-end"></a>

Front-End UI의 로딩 속도 향상을 위하여 3가지(PWA/Lazy-Load/Skeleton UI) Plug-in을 적용할 것임

#### 1) PWA <a href="#id-1-pwa" id="id-1-pwa"></a>

* PWA(Progressive Web App)란?\
  HTML, CSS, JavaScript와 같은 웹 기술로 만드는 앱을 말합니다.
* PWA의 장점
  * 익숙한 웹 기술 그대로 사용함으로 진입 장벽이 낮음
  * 푸시 알림, 오프라인캐시, HTTPS 사용
  * 웹 브라우저만 있으면 어디든 배포 가능
  * 홈 화면 추가를 통한 응용 프로그램으로 설치 가능
  * 빠른 실행 속도
  * 네이브 앱과 유사한 사용자 경험 제공
* PWA의 단점
  * 웹 API를 통하여 통신하므로 웹 표준 지원 브라우저 필요
  * 앱스토어, 플레이스토어 이용 불가
  * IOS는 일부 기능만 사용 가능

<figure><img src="https://tech.x2bee.com/download/attachments/2654357/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98_Front-End.png" alt=""><figcaption></figcaption></figure>

| 리소스 구성                     | 내용                                                                                                                                                                                                 |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p> </p><p>PWA Caching</p> | <ul><li>Application에서 API 서버 호출</li><li>Service Worker가 Caching된 data가 있는지 확인하여, 있으면 caching된 data를 먼저 화면에 보내고 API 요청</li><li>API의 respons를 Service Worker가 caching하고 application으로 보내 줌</li></ul> |

#### 2) Lazy - Load <a href="#id-2-lazy-load" id="id-2-lazy-load"></a>

* Lazy-Load 란?
  * 페이지에 액세스할 때, 모든 콘텐츠를 대량으로 로드하는 대신, 사용자가 필요한 페이지의 일부에 액세스할 때 콘텐츠를 로드할 수 있음
  * component , image 등에 주로 사용됨
* Lazy-Load 의 장점
  * 사용자가 처음 웹 페이지를 열 때 사이트의 일부만 다운로드하므로콘텐츠를 더 빠르게 제공
  * 모든 콘텐츠를 로드하는 게 아니므로 리소스 비용 감소
* Lazy-Load 의 단점
  * 페이지의 모든 콘텐츠를 불러오는 게 아니기 때문에SEO(Search Engine Optimization)에 상대적으로 취약함

#### 3) Skeleton UI <a href="#id-3-skeleton-ui" id="id-3-skeleton-ui"></a>

* Skeleton UI 란?\
  실제 데이터가 렌더링 되기 전, 보일 화면의 윤곽을 먼저 그려 주는 로딩 애니메이션
* Skeleton UI 의 장점
  * 로딩이 완료되면 윤곽에 데이터가 대체되어 화면이 부드럽게 전환되기 때문에 체감 로딩 시간이 짧음
* Skeleton UI 의 단점
  * 모든 페이지에 적용하기에는 시간과 비용이 많이 듦

| <p><img src="https://tech.x2bee.com/download/attachments/2654357/image-20220331-020335.png?version=1&#x26;modificationDate=1648692217742&#x26;cacheVersion=1&#x26;api=v2" alt="" width="375"></p> | <p><img src="https://tech.x2bee.com/download/attachments/2654357/image-20220331-020344.png?version=1&#x26;modificationDate=1648692226136&#x26;cacheVersion=1&#x26;api=v2" alt="" width="375"></p> |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### 성능 보장 방안 ( AutoScaling ) <a href="#id-5.-autoscaling" id="id-5.-autoscaling"></a>

<figure><img src="https://tech.x2bee.com/download/attachments/2654357/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98_AutoScaling.png" alt=""><figcaption></figcaption></figure>

| 리소스 구성             | 내용                                                                                                                                                                                                                                                    |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p> </p><p>HPA</p> | <p>Horizontal Pod Autoscaling</p><p>Pod로 서비스를 구성할 때 최소 개수와 최대 개수를 지정하면 부하가 없을 때 최소 수로 서비스를 제공하다가 부하가 많아지면 Node의 Reource가 허용하는 한도내에서 최대 개수까지 자동으로 Pod가 증가한다.</p><p>– 전체 서비스 중 필요한 서비스의 Pod만 증가하므로 MSA에서 매우 효율적인 Utilization과 빠른 Scale-out 성능을 보인다.</p> |
| Node Autoscaling   | AWS위의 Kubernetes에서는 할당된 Node의 자원을 모두 사용했을 때 (새로운 Pod가 스케줄되지 않을 때) 지정된 Node 개수만큼 Node를 자동으로 scale-out 하여 서비스 성능을 보장한다.                                                                                                                                 |

<a class="button secondary">
</a>


# UI/UX 가이드

## 개요

자유로운 설정 및 손쉬운 콘텐츠 구성을 목적으로 UI/UX 표준 가이드를 설명합니다.

X2BEE의 일관된 사용기준을 수립하고, 사용자의 행동유형과 인지체계를 반영한 시스템 환경을 안내합니다.

1. 유저가 사용 숙련도에 관계 없이 일관된 사용자 경험 제시
2. 개발 및 유지보수와 사용자 학습 효율을 높일 수 있는 화면 제작을 위한 기준 제시
3. 사용자 시지각성 확보와 정보의 구조적 배열을 반영한 기준 제시
4. 일관적이고 직관적인 사용자 환경 유지를 위해 지켜져야 할 요소 정의와 화면설계 원칙 제시

***

## 문서 구성

<details>

<summary><a href="https://tech.x2bee.com/space/TG/14352398">레이아웃</a> </summary>

X2BEE BO 화면에 대한 레이아웃에 대한 정보를 제공하고 있습니다.

</details>

<details>

<summary><a href="https://tech.x2bee.com/space/TG/14550050">화면 상세 가이드</a> </summary>

X2BEE BO 기능 사용을 위해 화면 설계 정보를 자세하게 설명합니다.

</details>

<details>

<summary><a href="https://tech.x2bee.com/space/TG/1015911">리얼그리드</a> </summary>

데이터 입력/수정/삭제에 대한 ‘리얼그리드(RealGrid)’라이브러리 사용 정보를 제공하고 있습니다.

</details>

<br>


# 화면 상세 가이드

X2BEE 백오피스의 화면 상세 가이드는 사용자에 대한 이해를 바탕으로 원활한 기능 사용을 위해 효율적으로 제공할수 있는 화면 설계 방안을 제공합니다.

화면 가이드 라인은 다음 콘텐츠 목록으로 구성되어 있으며, 관련 된 가이드라인 내용을 빠르게 찾아 볼 수 있습니다.


# 타이틀 지정

## 개요 <a href="#undefined" id="undefined"></a>

명확한 타이틀 스타일 사용으로 정보 계층 구조를 명확히 알 수 있습니다.

한 업무 화면 내 타이틀의 종류는 해당 업무 화면의 메뉴 화면명과 정보 그룹별 계층구조 타이틀로 구분합니다.

업무 화면 내에 계층구조는 불릿 아이콘과 함께 폰트 요소로 타이틀를 구분합니다.

## 타이틀 적용의 경우\_계층관계 <a href="#id" id="id"></a>

<figure><img src="/files/oJzRVdkiGHDjB0qOt9h3" alt=""><figcaption></figcaption></figure>


# 버튼 생성 가이드

## 버튼명 표기 방법 <a href="#undefined" id="undefined"></a>

1. 모든 버튼 명 표기는 명사로 표기 합니다.
   * \[저장], \[확인], \[다음], \[이전] 등
   * 동사 또는 형용사의 명사형 표기는 사용을 제한 합니다.\
     ex) \[저장하기]
2. 버튼명 작성시 기능 타이틀 대신 사용자의 동작을 표현하는 버튼명을 사용합니다.
   * 업무 및 기능의 표현은 작업공간 화면에 정보가 포함되어있어 사용자의 동작을 나타내는 명칭을 사용합니다.
   * 사용자의 동작으로 표현 시 버튼 기능에 대한 해석의 혼선을 방지하기 위한 경우는 예외로 작업/기능 명을 앞에 붙일 수 있습니다.
     * 동일 화면에서 서로 다른 2가지 이상의 작업을 처리해야하는 경우 버튼이 필요한 작업 명 + 동작 ex) \[품절상품 목록]
3. 유니크한 작업은 버튼 명으로 사용이 필요한 경우 ex) \[번역요청]. \[초기화] 등
4. 버튼 클릭 시 팝업을 띄워 작업을 처리하는 경우는 ‘↗︎’ 아이콘을 사용하여, 팝업이 뜨는 시각적 표현으로 정보를 제공합니다.
5. 팝업, 연관 화면 버튼의 경우, 대상화면명을 버튼명으로 지정이 가능합니다.
6. 실행 버튼의 순서는 화면 기능에 따라 선택 나열 합니다.

## 버튼 화면 표시 사이즈 <a href="#undefined" id="undefined"></a>

* Text 버튼 최소 사이즈 : 60\*36
* 아이콘 버튼 : 20\*20

<figure><img src="https://tech.x2bee.com/download/attachments/14517138/UIUX%20-%20Button.png" alt=""><figcaption></figcaption></figure>


# 탭(Tab) 적용 가이드

X2BEE의 메뉴의 작업 공간을 구분하고 탭을 클릭하여 작업 공간의 화면을 이동하는 역할을 합니다.

탭을 구성하는 요소와 활용 정보를 설명합니다.

***

## 기본 구성 요소 <a href="#undefined" id="undefined"></a>

1. 탭이 정의하는 해당 정보의 상단에 탭 그룹을 구성합니다.
2. 입력이 가능한 탭의 경우 저장 버튼은 화면 단위로 저장할 수 있게 공통 영역에 포함하여 구성합니다.
3. 탭 개수가 많은 경우, 탭 방향키를 사용할 수 있습니다. (Tab in Tab(세로형) 이중탭은 사용하지 않습니다.)

## 배치 <a href="#undefined" id="undefined"></a>

가장 중요하고 선택 빈도가 높은 항목 또는 작업 순서 상 첫번째 탭을 좌측에 배치하고 활성화 합니다.

탭 배치를 위해 다음 가이드를 참고해야 합니다.

* 탭은 한 줄로 구성하며 두 줄로 표현하지 않음
* 선택 탭은 선택 되지 않은 탭과 구분할 수 있도록 시각적으로 명확하게 표현해야 함
* TabControl의 탭의 개수는 브라우저의 메모리 및 로딩 속도를 고려해 5개의 탭 생성을 권장하며, 최대 10개까지 생성을 제한함

<figure><img src="https://tech.x2bee.com/download/attachments/14975047/%ED%83%AD%20%EC%A0%95%EB%A0%AC.png" alt=""><figcaption></figcaption></figure>

## 정보 유형별 탭 <a href="#undefined" id="undefined"></a>

A타입

같은 조회조건으로 검색된 동일한 레벨의 정보 그룹을 탭으로 나타낸다.

동일레벨의 정보가 분할하여 보여지는 타입과 업무 처리 순서에 따라 배열하는 프로세스형 타입으로 사용됩니다.

<figure><img src="https://tech.x2bee.com/download/attachments/14975047/tab%20tayp.png" alt=""><figcaption></figcaption></figure>

B타입

관련 화면을 모아서 업무 순서대로 배열한 방식 탭 안에 개별 조회 영역을 포함할 수 있습니다.

<br>

<figure><img src="https://tech.x2bee.com/download/attachments/14975047/Untitled%20Diagram.drawio.png" alt=""><figcaption></figcaption></figure>


# 레이아웃

본 문서에서 X2BEE 프로젝트의 표준 레이아웃에 대하여 기술합니다.

레이아웃 해상도와 UI구현 컨셉에 대하여 설명합니다.

***

## 해상도 <a href="#undefined" id="undefined"></a>

* 해상도는 스크린 점유율 통계를 기반으로 결정
* PC 웹과 모바일 웹을 기준으로 하며 브라우저 별 변경 사이즈는 CSS미디어 쿼리로 정의
* 태블릿의 경우 PC버전으로 구현
* 웹의 최저 해상도는 1003px

&#x20;

<div align="left"><figure><img src="https://tech.x2bee.com/download/attachments/14352398/%EB%AA%A8%EB%8B%88%ED%84%B0%20%ED%95%B4%EC%83%81%EB%8F%84.png" alt=""><figcaption></figcaption></figure></div>

## UI구현 컨셉 <a href="#ui" id="ui"></a>

### 메인프레임 레이아웃 <a href="#undefined" id="undefined"></a>

<figure><img src="https://tech.x2bee.com/download/attachments/14352398/Untitled%20Diagram.drawio.png" alt=""><figcaption></figcaption></figure>

| 기본구성                              | Top/left/work/Bottom 4분활 구조                                               |
| --------------------------------- | ------------------------------------------------------------------------- |
| GNB (Global Navigation Bar)       | 시스템 LOGO 전체 단위 시스템 이동                                                     |
| LNB (Local Navigation Bar)        | 로컬메뉴(1\~3), 마이메뉴, 전체 시스템 메뉴(xx, xx, xx)                                   |
| 작업 공간(Work Area)                  | <p>단위 메뉴 화면의 업무처리가 이루어지는 영역<br>단위와 관련된 기능(마이 메뉴 등록, 화면 잠금 기능, 도움말) 노출</p> |
| MDI((Multiple document interface) | 오픈 된 메뉴 화면 탭으로 보여짐                                                        |

### 화면 스크롤 기본 정책 <a href="#undefined" id="undefined"></a>

업무영역은 기준 해상도에서 가로 스크롤이 생성되지 않는 것을 원칙으로 하고 업무영역의 세로방향은 가급적 스크롤이 생성되지 않은 것을 권장합니다.

Grid Text Area 컴포넌트 내부의 가로 또는 세로스크롤 생성은 허용합니다.

기준 행상도 1920\*1080/사용자 작업 공간 1360\*1040

<figure><img src="https://tech.x2bee.com/download/attachments/14352398/frame%20scroll.png" alt=""><figcaption></figcaption></figure>

### 작업 공간 유형 <a href="#undefined" id="undefined"></a>

<br>

<figure><img src="https://tech.x2bee.com/download/attachments/14352398/%EC%A0%9C%EB%AA%A9%20%EC%97%86%EB%8A%94%20%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8-1684455402143.drawio-aaa74d4878d6110e1eb4b7df7c87532df0f9c79d.png" alt=""><figcaption></figcaption></figure>


# 리얼그리드

BO 템플릿은 목록조회 및 목록데이터 관리(입력/수정/삭제)에 리얼그리드(RealGrid)라는 외부 자바스크립트 라이브러리를 사용했습니다. ※ 본 문서는 구 X2BEE 3.0 BO(Thymeleaf 템플릿) 기준입니다. 현행 3.1 BO는 Next.js 기반으로 MUI X Premium DataGrid(@mui/x-data-grid-premium 7.21.0)를 사용하므로, 아래 RealGrid·HTML·JS 예시는 레거시 참고용입니다.

BO 프로그램 전반에 걸쳐서 광범위하게 사용되고 있어서, 그리드를 활용하기 위한 개발구조 및 편의기능을 제공하고 있습니다.

## realgrid HTML

그리드에 사용될 form과 그리드가 생성될 위치를 정의합니다.

그리드 생성과 그리드 내에서 사용하는 데이터 조작을 위한 이벤트가 정의된 js파일을 가져옵니다.

```html
<!-- B 영역 : (그리드) 하위 전시 카테고리 매장 조회 목록 -->
<div class="grid-cont" id="subCategoryGridArea"> <!-- 그리드 <div> 영역. realgrid 컨트롤 + 데이터 조회/수정을 위한 <form> + 조작버튼 + 페이징정보 를 포함한 전체 <div> 영역입니다. -->
   <div class="grid" style="overflow: hidden;">
       <div class="grid-head">
           <form name="subCategoryGridForm" id="subCategoryGridForm"> <!-- 데이터 조회/수정 시 데이터가 전송 시 realgrid에서 사용된 <form>입니다. -->
               <input type="hidden" id="subCategoryGrid_uprDispCtgNo" name="uprDispCtgNo">
               <input type="hidden" id="subCategoryGrid_siteNo" name="siteNo">
               <input type="hidden" id="subCategoryGrid_dpmlNo" name="dpmlNo">
               <input type="hidden" id="subCategoryGrid_shopTypCd" name="shopTypCd">
               <input type="hidden" id="subCategoryGrid_thnCtgNo" name="thnCtgNo">
           </form>
           <div class="title-area">
               <h2 class="title" th:text=“#{'displayCategoryMgmt.subCategoryGrid.title'}" />
           </div>
           <div class="option-area">
               <div class="edit-option">
                   <div class="button-group"> <!-- 데이터 조작을 위한 버튼을 표시합니다. 버튼 클릭 시 자바스크립트 코드는 개별적으로 작성해야합니다. -->
                       <a href="#" class="button inside" id="btn_subCategoryGrid_add">
                           <span class="text" th:text="#{'adminCommon.grid.button.add.row'}" />
                       </a>
                       <a href="#" class="button inside" id="btn_subCategoryGrid_save">
                           <span class="text" th:text=“#{'adminCommon.button.save'}" />
                       </a>
                   </div>
               </div>
               <div class="page-option" grid-id="subCategoryGrid"> <!-- 페이징 정보가 표시됩니다. -->
                   <span class='total'>총 <span id="subCategoryGrid-totalcount">0</span>건</span>
               </div>
           </div>
       </div>
       <div class="grid-body with-head vh2">
           <div id="subCategoryGrid" realgrid class="vh2"></div> <!-- realgrid 컨트롤이 표시되는 영역입니다. -->
       </div>
   </div>
</div>
<!-- B 영역 : (그리드) 하위 전시 카테고리 매장 조회 목록 -->

<!-- 자바스크립트 파일 지정 및 초기화 스크립트 실행 -->
<script type="text/javascript" th:src="${@domainConfig.getProperty('jsUrl')} + 'system/bwGrid.eventHandler.js'"></script>
<script type="text/javascript" th:src="${@domainConfig.getProperty('jsUrl')} + 'system/bwGrid.provider.js'"></script>

<script type="text/javascript">
   $(function() {
       bwGrid.eventhandler.init();
   });
</script>
```

## realgrid provider.js

x2bee에서 realgrid 사용 시 provider.js와 eventHandler.js 를 작성해야 합니다.

provider.js 파일에 대해서 설명합니다.

provider.js 에서는 그리드 내에 들어갈 데이터의 전반적인 내용 및 속성을 정의합니다.

정의할 속성으로는 필드명, 컬럼속성, 데이터검증, 그리드속성 등이 있습니다.

```javascript
$.namespace("bwGrid.settings");
bwGrid.settings = { //그리드의 필드명지정
   fields : [ {
       fieldName : "bwSeq"
   }, {
       fieldName : "sysModId"
   } ],
   columns : [ { //컬럼속성지정
       name : "bwNm",
       fieldName : "bwNm",
       header : {
           text : col.bwNm + "*"
       },
       width : 200,
       editable : true,
       styleName : "left-column",
       editor :{
           maxLength: 30
       }
   }, {
      ...
   } ],
   validations : [ //데이터 검증 지정
       {
           fieldName : "bwNm",
           criteria : "value === undefined || value.trim() === ''"  ,
           error : { level : "error", message : alertMsg.bwNmMessage }
       }
   ],
   props : { //그리드 속성지정, api URL 지정등의 작업 수행
       paging : true,
       width : "100%",
       autoFitHeight : true,
       checkbox : true,
       crud : true,
       form : "bwGridForm", //데이터 조회/수정 시, 데이터 전송 시 realgrid 에서 사용될 form ID 지정
       sumRowVisible : false,
       action : _baseUrl + "system/badWordMgmt.getBadWordList.do", //조회 시 url 지정
       saveAction : _baseUrl + "system/badWordMgmt.saveBadWord.do" //저장 시 url 지정
       //rows : [100,500,1000] //rows 설정시 rowsPerPage 설정 가능, default [100,500,1000,5000,10000]
   }
};
```

## realgrid eventHandler.js

eventHandler.js 파일에 대해 설명합니다.

eventHandler 에서는 그리드 내에서 사용자가 변경하는 데이터에 대한 삽입,수정,삭제등의 처리를 지정합니다.

그리드 기본설정 스크립트, 그리드 내의 버튼이벤트, 사용되는 함수를 정의합니다.

```javascript
$.namespace("bwGrid.eventhandler");

$('#btn_grid_remove').click(function() {
    self.grid.cancel();
    self.onDelete();
});

$("#btn_grid_reset").click(function() {
    self.onReset();
});

$('#btn_grid_save').click(function() {
    self.onSave();
});

$("#bwGridForm").keypress(function (e){
    if (e.which == 13){
        $('#btn_search').click();
        window.event.returnValue=false;
    }
});

}, // (end init or surrounding object)
gridEvent : { //그리드 이벤트 설정
    afterSaveSuccess : function(eventHandler, mainGridName, gridNames, data){
        openToast(data.message);
        if(data.succeeded){
            eventHandler.onSearch(0,false);
       }
    },
    onEditRowChanged: function (grid, itemIndex, dataRow, field, oldValue, newValue) {
        var rowCnt = grid.getItemCount();
        var editRowIndex = itemIndex;

        var rowData = "";
        for (var rowIndex = 0; rowIndex < rowCnt; rowIndex++) {
            if (rowIndex == editRowIndex) continue;

            rowData = grid.getValue(rowIndex, "bwNm");
            if ( newValue === rowData ) {
                alert(alertMsg.dupMessage);
                grid.cancel();
                break;
            }
        }
    }
},
onAdd : function() { //함수 정의
    var self = this;
    var grid = self.grid;
    grid.commit(true);

    var defaultValues = { useYn : "Y" };
    self.defaultHandler.onAdd(grid, defaultValues);
},
onDelete : function() {
    var self = this;
    var grid = self.grid;

    var checkedItems = grid.getCheckedItems();
    if (!checkedItems.length) {
        alert(alertMsg.rowCheckMsg);
        return;
    }

    self.defaultHandler.onDelete(grid);
},
onReset : function() {
    var grid = this.grid;
    this.defaultHandler.onCancel(grid);
},
onSearch : function(pageIdx,isOpenToast){
    this.grid.cancel();
    pageIdx = !pageIdx ? 0 : pageIdx;
    var self = this;
    var pagingFunc = function(pageIdx){return self.onSearch(pageIdx);};

    this.controller.doQuery(this, "", pageIdx, pagingFunc, false , isOpenToast);
},
onSave : function() {
    var grid = this.grid;
    this.controller.doSave(this, grid.localId);
}
};
```


# 스타일 가이드

X2BEE 3.0 BO 시스템 UI/UX 가이드는 고객의 브랜드 아이덴티티를 효과적으로 반영할 수 있도록 설계되었습니다. X2BEE 고객사의 아이덴티티를 적용할 수 있는 기준을 제공하며 다음과 같은 기대효과를 목표로 작성되었습니다.

1. 사용자 경험의 제고와 이용자 만족도 향상
2. 사용자 경험을 향상하기 위한 접근 방법과 지침 제시
3. UI/UX 개발 및 관리 투입 비용 절약

#### 적용 범위 <a href="#undefined" id="undefined"></a>

X2BEE 3.0 솔루션 기반 온라인 커머스 BO 시스템

* 색상(color), 서체(typography), 형태(Shape), 배치(Layout), 아이콘(Icon) 요소와 함께 사용 규칙들을 준수하여 일관된 아이덴티티를 유지하도록 합니다.

| **Color**                                                                                                                                                                                     | **Typography**                                                                                                      | **Layout**                         | **System icon**                                                              |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- |
| <p>Key colors</p><ul><li>Primary color</li><li>Secondary color</li><li>Grayscale</li><li>Alpha</li></ul><p>Usage</p><p>Point colors</p><p>System Colors</p><p>Palette</p><p>Accessibility</p> | <p>Typeface</p><p>Type Scale</p><ul><li>Font size</li><li>Font weight</li><li>Line height</li></ul><p>Hierarchy</p> | <p>Header</p><p>Nav</p><p>Grid</p> | <p>Base</p><p>Selections</p><p>Arrow</p><p>Info</p><p>Common</p><p>Ohter</p> |

***

### 색상(Color) <a href="#color" id="color"></a>

고객사를 대표하는 색상을 기반으로 디지털 UI에 최적화한 색상으로 구성합니다. 색상 대비와 접근성 기준(WCAG)을 준수하여 사용자 환경에서 가독성과 시각적 일관성을 유지합니다.

* Primary Color: 주요 UI 구성 요소(버튼, 활성 상태 등)에 사용
* Secondary Color: 필터, 칩과 같이 보조적인 역할을 하는 구성 요소에 사용
* Grascale: 배경 및 텍스트 대비를 위한 색상
* Alpha: 단계 투명도로 구성
* Gradient: Light, Main, Bold로 구분

#### 사용지침 <a href="#undefined" id="undefined"></a>

* Color Palette는 밝은 색상부터 어두운 색상까지 5단계로 사용
* &#x20;

***

### 서체(Typography) <a href="#typography" id="typography"></a>

#### Typeface <a href="#typeface" id="typeface"></a>

* 기본 서체: 프리텐다드(Pretendard)
* 선택 서체: 나눔고딕, 퍼블릭산스, IBM 플렉스 산스체

#### Type scale <a href="#type-scale" id="type-scale"></a>

| **Style**  | **Size** | **Font weight** | **Line height** | **Letter spacing** | **Usage**         |
| ---------- | -------- | --------------- | --------------- | ------------------ | ----------------- |
| Main Title | H5       | 16px            | 700             | 26px               | 메뉴 페이지 단위 타이틀에 사용 |
| Sub Title  | H6       | 14px            | 700             | 24px               |                   |
| Body       |          | 12px            | 600             | 22px               |                   |
| Body1      |          | 13px            | 400             | 24px               |                   |
| Label      | Large    | 24px            |                 |                    | 버튼                |
| Medium     | 20px     |                 |                 |                    |                   |
| Small      | 18px     |                 |                 |                    |                   |

#### 사용 지침 <a href="#undefined" id="undefined"></a>

* 중요 콘텐츠와 보조 콘텐츠의 계층을 명확히 구분
* 반응형 디자인을 고려하여 폰트 크기는

***

### 배치(Layout) <a href="#layout" id="layout"></a>

화면에 있는 요소들의 시각적 배열을 다양한 디바이스에서도 일관성을 유지할 수 있도록 작업영역을 기준 준수를 권장합니다.

#### 1. 중단점(Breakpoint) <a href="#id-1.-breakpoint" id="id-1.-breakpoint"></a>

디바이스 크기에 따라 중단점은 4가지 min-width(최소 가로값)로 정의되어 있습니다.

| **Device** | **Breakpoint**  | **대표 화면 사이즈**         |
| ---------- | --------------- | --------------------- |
| PC/Desktop | 1025 \~ 1920 px | XL: 1536px, L: 1200px |
| Tablet     | 601 \~ 1024px   | 900px                 |
| Mobile     | 360 \~600px     | 600px                 |

#### 2. 화면 구성 <a href="#id-2" id="id-2"></a>

다양한 디바이스에서 보이는 화면의 최대 콘텐츠 영역을 설정하여 사용합니다. 디바이스 크기가 커져도 동일한 배치를 유지하는 제한된 콘텐츠 영역을 사용하며, 최댓값 1200px을 적용합니다. 반응형을 기반으로 레이아웃을 구성하며 디바이스 축소 시에는 디바이스 크기에 맞게 콘텐츠가 재배치 됩니다.

![레이아웃-기본구성.png](https://tech.x2bee.com/download/attachments/525729859/%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EA%B8%B0%EB%B3%B8%EA%B5%AC%EC%84%B1.png?version=1\&modificationDate=1733726215215\&cacheVersion=1\&api=v2)

| Area      | 사용                                                                                     |
| --------- | -------------------------------------------------------------------------------------- |
| 기본 구성     | Top, Left, Work 3분할 구조를 사용합니다.                                                         |
| GNB       | 메뉴 검색, 설정, 사용자 정보 확인을 제공하는 컴포넌트를 배치합니다.                                                |
| LNB       | <p>전체 시스템 메뉴 정보를 배치, 구성하여 페이지를 이동할 수 있습니다.</p><p>메뉴 접기 기능을 제공해 작업영역을 더 확보할 수 있습니다.</p> |
| Work Area | 다양한 컴포넌트로 구성되어 있으며, 메뉴의 업무를 처리할 수 있습니다.                                                |

#### 3. 작업 공간 유형 <a href="#id-3" id="id-3"></a>

작업 공간은 메뉴에 따라 다른 타입으로 제공되며, 크게 4가지 유형으로 구분합니다. 모바일의 경우 반응형으로 제작되어 다른 작업 공간 유형으로 제공하나 데스크탑과 동일한 정보를 제공해야 합니다.

<figure><img src="/files/N9gVOHXPfhhs9Kqld5Tk" alt=""><figcaption></figcaption></figure>

#### 4. 구성 별 간격 <a href="#id-4" id="id-4"></a>

페이지 내 콘텐츠 영역은 다음과 같은 간격을 유지하고 데스크탑에서 모바일 사이즈로 변화되는 간격에 유의해야 합니다.

<figure><img src="/files/lbAUs6LQ54ZaBwpcbrm7" alt=""><figcaption></figcaption></figure>

작업영역은 메뉴명 영역과 조회/조회결과 영역으로 구분되며 콘텐츠 단락 사이의 간격을 아래와 같이 준수합니다.

| **Area**             | **데스크톱(1200기준)** | **모바일(600기준)** |
| -------------------- | ---------------- | -------------- |
| GNB                  |                  |                |
| LNB                  |                  |                |
| Heading-Body         |                  |                |
| 조회 및 조회 결과 영역 - Body |                  |                |
| 조회 목록 버튼 - Body      |                  |                |
| Body - Footer        |                  |                |

&#x20;

***

### 시스템 아이콘(System Icon) <a href="#system-icon" id="system-icon"></a>

#### 기본 규칙 <a href="#undefined" id="undefined"></a>

* SVG 파일 사용
* 표준 아이콘의 사이즈는 24px이며, 16px, 18px. 20px, 32px, 40px 사이즈 사용
* Google Material Symbol/Rounded/400 을 기본 사용

#### 사용 지침 <a href="#id-1" id="id-1"></a>

* 라인 타입(Line type)과 필 타입(Fill type)을 구분하여 사용
* 명확한 정보 전달을 위해 일관된 스타일 유지

&#x20;

***

### 버튼(Button) <a href="#button" id="button"></a>

#### 작성 규칙 <a href="#undefined" id="undefined"></a>

1. 버튼 명은 명사로 표기 (\[저장], \[확인] 등)
2. 버튼의 기능은 사용자 동작을 중심으로 표현
3. AI 기능 및 다국어와 같이 특수 작업 버튼은 별도 스타일 제공

&#x20;

#### 사용지침 <a href="#id-1" id="id-1"></a>

* 팝업, 연관 화면 버튼의 경우, 대상화면명을 버튼명으로 지정
* 실행 버튼의 순서는 화면 기능에 따라 선택 나열

&#x20;

***

### 접근성 <a href="#undefined" id="undefined"></a>

* WCAG 2.1 AA 레벨 기준 충족
* 색상 대비, 텍스트 크기 및 키보드 내비게이션 지원


# 컴포넌트

#### 아이덴티티 <a href="#undefined" id="undefined"></a>

* 색상
* 서체
* 쉐도우
* 그리드
* 아이콘
* 로고
* 푸터
* 헤더

#### 탐색 <a href="#undefined" id="undefined"></a>

* 메인 메뉴
* 사이드 메뉴
* 탐색
* 페이지네이션

#### 레이아웃 및 표현 <a href="#undefined" id="undefined"></a>

* 구조화 목록
* Alert
* 달력
* 레이블

&#x20;

#### 액션 <a href="#undefined" id="undefined"></a>

* 링크
* 버튼

#### 선택 <a href="#undefined" id="undefined"></a>

* 라디오 버튼
* 체크박스
* 셀렉트

#### 피드백 <a href="#undefined" id="undefined"></a>

* 스피너

#### &#x20;<a href="#undefined" id="undefined"></a>

#### 도움 <a href="#undefined" id="undefined"></a>

* 패널

&#x20;

#### 입력 <a href="#undefined" id="undefined"></a>

* 날짜/시간 입력
* 텍스트 영역
* 텍스트 입력 필드
* 파일 업로드


# 패턴

## 개요

컴포넌트 요소들이 조합되어 메뉴 화면을 개발할 때 반복적으로 함께 사용 되는 사용자 인터페이스 집합에 대한 가이드입니다.

***

## 문서 구성

<details>

<summary><a href="/pages/wuanK93DfSAPbyoTnYSv">조회 영역 상세 가이드</a></summary>

</details>

<details>

<summary><a href="/pages/xtLkfV4CNurm5jZQneFG">그리드 화면 가이드</a></summary>

</details>

<details>

<summary><a href="/pages/suqRNl2f5SzrO7x4icXE">트리 구성 및 배치</a></summary>

</details>

<details>

<summary><a href="/pages/TO10EfJ2SSDl8U785dng">팝업 UI 가이드</a></summary>

</details>

<details>

<summary><a href="/pages/mNQoX15gHEfJApm2S2YJ">지시문/오류/주석 가이드</a></summary>

</details>


# 조회 영역 상세 가이드

### 조회 및 데이터 항목 <a href="#undefined" id="undefined"></a>

조회 영역은 데이터를 조회하기 위한 조건을 설정하는 영역으로 결과 데이터 영역의 상단에 위치합니다.

메뉴를 클릭하여, 화면이 나타난 경우, 조회 영역의 활성화된 가장 처음에 포커스를 제공합니다.

List & Detail 조회 시 한 화면에서 목록과 상세 CRUD 처리하는 화면의 조회 영역은 상/하, 좌/우로 구성할 수 있습니다.

* 조회 영역 예시

<figure><img src="https://tech.x2bee.com/download/attachments/14680085/Untitled%20Diagram.drawio.png" alt=""><figcaption></figcaption></figure>

&#x20;

#### 1. 조회 조건 항목  <a href="#id-1" id="id-1"></a>

* 콤보 박스는 선택 형 조회 조건의 경우에 우선 적용합니다.
  * 30건 이하 콤보 구성 - 그 이사 팝업으로 처리
  * 디폴트 옵션 또는 “선택”을 반드시 표시
  * 전체 선택 빈도가 높을 시 예외적으로 ‘전체’를 디폴트 옵션으로 적용 가능
* 팝업화면은 선택형 조회조건 대상 데이터가 30건 이상 일 때 적용합니다.
* 라디오 버튼은 예/아니오 등 향후 추가 될 데이터가 없는 선택 조회 조건시 사용합니다.\
  예외) 선택 데이터를 한눈에 나타내야하는 경우 제한적으로 사용
* 체크박스는 복수 선택이 필요할때 사용합니다.

<figure><img src="https://tech.x2bee.com/download/attachments/14680085/%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%A1%B0%EA%B1%B4.png" alt=""><figcaption></figcaption></figure>

&#x20;

#### 2. 필수 조회 조건  <a href="#id-2" id="id-2"></a>

필수 입력 표시는 항목에 별도로 필수 입력 표기를 진행합니다.

<figure><img src="https://tech.x2bee.com/download/attachments/14680085/%ED%95%84%EC%88%98%20%ED%95%AD%EB%AA%A9%20%ED%91%9C%EA%B8%B0.png" alt=""><figcaption></figcaption></figure>

&#x20;

### 조회 조건 항목 상세 <a href="#undefined" id="undefined"></a>

#### 1. 항목 배치 <a href="#id-1" id="id-1"></a>

조회 조건 항목의 배치는 중요도, 사용 빈도, 범위가 큰 순서를 먼저 배치합니다.

* 관련있는 조건은 좌우로 나란히 배치합니다.
* 중요도에 따라 예외 사항을 둘 수 있습니다.
* 항목명은 좌측 정렬을 원칙으로 합니다.

<figure><img src="https://tech.x2bee.com/download/attachments/14680085/%EC%A1%B0%EA%B1%B4%20%EC%A1%B0%ED%9A%8C%20%EB%B0%B0%EC%B9%98%201-c7b6b689ed4a6247d4bb1f20f3d1f15976f09ba1.png" alt=""><figcaption></figcaption></figure>

#### 2. 항목 배치 순서 <a href="#id-2" id="id-2"></a>

항목의 배치는 작업 흐름에 따라 배치 합니다.

* 1 → 2 → 3 → 4 순으로 배치합니다.
* 이동 방향은 Z방향이며, Tab키를 클릭해 이동할 수 있습니다.

![](https://tech.x2bee.com/download/attachments/14680085/%EC%A1%B0%EA%B1%B4%20%EC%A1%B0%ED%9A%8C%20%EB%B0%B0%EC%B9%98.png)

#### 3. 조회 버튼 위치 <a href="#id-3" id="id-3"></a>

조회 버튼은 전체 영역 버튼으로 배치합니다.

<figure><img src="https://tech.x2bee.com/download/attachments/14680085/%EC%A1%B0%ED%9A%8C%20%EC%A1%B0%EA%B1%B4%20%EB%B0%B0%EC%B9%98%203.png" alt=""><figcaption></figcaption></figure>

#### 4. 자동 완성 기능 <a href="#id-4" id="id-4"></a>

자동 완성 기능 적용 예시는 다음과 같습니다.

* 자주 사용하는 조회 정보가 있는 경우 기본값을 제공합니다.
* 사전에 연동되는 정보가 있는 경우 조회 영역에 데이터를 표시하고 조회 결과를 노출합니다.
* 검색어를 입력하여 조회 하는 경우 자동완성 기능을 사용됩니다.

<figure><img src="https://tech.x2bee.com/download/attachments/14680085/%EC%A1%B0%ED%9A%8C%20%EC%A1%B0%EA%B1%B4%20%EB%B0%B0%EC%B9%98%204.png" alt=""><figcaption></figcaption></figure>


# 그리드 화면 가이드

X2BEE 작업공간에서 활용하는 그리드 화면과 적용할 수 있는 기능을 설명합니다.

그리드 업데이트 내역에 따라 신규 기능 추가 및 사용 방법이 변경 될 수 있습니다.

***

## 그리드 검색 <a href="#undefined" id="undefined"></a>

**일반 검색**

해당 그리드 내 데이터 검색이 가능하고 필터 기능을 대체하여 사용할 수 있습니다.

멀티 다중 데이터 처리 화면에서 사용합니다.

<figure><img src="https://tech.x2bee.com/download/attachments/14648524/%EA%B7%B8%EB%A6%AC%EB%93%9C%20%EA%B2%80%EC%83%89_%EC%9D%BC%EB%B0%98%20%EA%B2%80%EC%83%89.png" alt=""><figcaption></figcaption></figure>

**테이블내 인풋 검색**

테이블 안에 포함된 기능 버튼은 아이콘으로 표기를 원칙으로 합니다.

입력필드(normal, readonly, disable)+\[검색] 버튼(+\[삭제] 버튼)의 순서로 구성됩니다.

## 그리드 유형 <a href="#undefined" id="undefined"></a>

그리드는 ‘기본 조회형’과 ‘데이터 처리형’ 두가지 유형으로 가이드를 제공합니다.

기본 구성은 조회를 통해 데이터를 출력하는 경우 상단 조회 하단 그리드로 구성됩니다.

두개이상의 그리드간의 데이터가 계층구조로 되어있을 경우 상위 정보 그리드는 좌측과 상단에 위치합니다.

**기본 조회형 그리드**

데이터 조회를 목적으로 하는 화면에서 데이터를 구조화해 제공하는 경우에 사용합니다.

사용을 위한 가이드는 다음과 같습니다.

1. 그리드 내 선택 기능은 행/셀 선택 모두 제공하며, 선택 시 시각적 피드백이 명확해야 합니다.
   * 행(uine) 단위선택 : 행 단위로 하이라이트 표기(ex. 백그라운드)
   * 셀 단위 선택 : 정보가 있는 경우 Data가 있음을 표기 (ex. 텍스트언더라인)
2. 2개 이상의 그리드로 구성된 경우 메인 그리드의 선택 항목에 대한 시각적 피드백이 명확해야 합니다.
3. 건수 표기가 있는 경우, 그리도 맨 앞에 ‘No.’로 Number를 표기합니다.
4. 조회 조건에의 데이터가 출력된 다건 삭제 기능이 포함된 그리드는

기본 조회형 그리드 예시

<figure><img src="https://tech.x2bee.com/download/attachments/14648524/Grid-%EA%B8%B0%EB%B3%B8%EC%A1%B0%ED%9A%8C%ED%98%95.png" alt=""><figcaption></figcaption></figure>

**데이터 처리형 그리드**

적용 데이터 조회 후 한번에 여러 행의 데이터를 수정, 삭제, 신규 처리

1. 여러행의 데이터를 그리드에서 바로 수정/삭제/입력이 가능합니다.
2. 다건을 처리하는 그리드 화면에서 좌측 컬럼 상태 Flag로 변경/삭제/추가 정보를 표시할 수 있습니다.
3. 데이터 수정/삭제/입력 시 그리드 앞부분에 상태 표시가 되며 \[저장] 버튼을 클릭하여 한번에 DB에 반영 합니다.

데이터 처리형 그리드 예시

<figure><img src="https://tech.x2bee.com/download/attachments/14648524/Grid-%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%B2%98%EB%A6%AC%ED%98%95.png" alt=""><figcaption></figcaption></figure>

## 그리드 정보 배치  <a href="#undefined" id="undefined"></a>

1. 그리드의 Col 순서는 No(기본제공) → 체크박스 → 내용의 순으로 배치합니다.
   * Col 넓이 : 출력되는 데이터의 길이에 맞춰 설계자가 지정한 넓이 대로 설정
   * 정형화된 데이터 길이(날짜, 계좌번호, 전화번호, 주민번호 등)가 있는 것은 해당 데이터 내용이 모두 보여지게 입력폼의 길이 표준화 진행
   * 비정형 형태의 데이터 길이를 가지는 것을 설계 궈장 MaxLength에 맞춰 길이 조절.(\*권장사항)
2. 데이터의 가독성 향상을 위해 짝수행이 밝은 회색 배경 컬러 삽입 합니다.
3. 헤더 영역의 text 정렬기준
   * 상단 헤더(thead-th)의 정렬 기준 → 중앙정렬
   * 좌측헤더(thody-th)의 정렬 기준 → 좌측정렬
4. 데이터 영역의 text 정렬 기준
   * 가운데 정렬 : 상품 번호, 성명, 일자, 기간, 번호(휴대폰, 전화, 팩스), 등록번호(사업자, 법인, 주민) 코드성 데이터를 포함하여 데이터 길이가 14Byte이내
   * 좌측정렬 : 가변 데이터 길이를 가진 서술형 정보
   * 우측정렬 : 금액, 수량 등 숫자 비교가 필요한 데이터

## 그리드 동작 가이드 <a href="#undefined" id="undefined"></a>

1. 전체 Row 선택/해제가 필요한 경우 헤더에 있는 체크박스를 사용합니다.
2. 모든 그리드에 소트(Sortable=true)를 적용합니다.
3. 그리드 순번에 관한 ‘No’(RowNumVisble=true)는 기본 표기하며, 업무 성격에 따른 불필요한 경우에는 사용하지 않아도 됩니다.
4. 관리자 고객 리스트 형식은 동일하며 많은 데이터 처리를 위해 내부 스크롤을 권장하며, 데이터 틀고정 필터링 등의 기능 사용이 가능합니다.
5. ‘No’ 클릭할때 행이 활성화 되며 셀 클릭할때 해당 셀과 No만 활성화
6. 클릭/온/오버/값에 따른 셀 변화는 다음과 같습니다.
   * Over/No 클릭 시 행이 활성화 되며 셀 클릭시 해당 셀과 No만 활성화 됩니다.
   * 오버-해당 셀의 활성화(백그라운드 컬러) 및 기능 버튼 추가
   * 원클릭 - 활성화(백그라운드 컬러 + 박스 영역 추가)
   * 더블클릭 - 변경 및 추가각 가능한 셀일 경우 입력 모드로 변환 됩니다.


# 트리 구성 및 배치

## 메뉴 목록 <a href="#undefined" id="undefined"></a>

![](https://tech.x2bee.com/download/attachments/14942914/UI_UX_%ED%8A%B8%EB%A6%AC.png)

&#x20;

* 계층을 이루는 정보를 트리 구조로 표현할 수 있습니다.
* 그룹핑 정보를 한눈에 불 수 있도록 구조화된
* 선택된 데이터가 속한 상위레벨을 파란색으로 표시하여 소속관계를 명확히 나타냅니다.

## 화면 목록 <a href="#undefined" id="undefined"></a>

* 계층을 이루는 데이터에 대해여 Row 단위의 나열식 표현
* 조회 기반의 정보 확인 유형

<br>

<figure><img src="https://tech.x2bee.com/download/attachments/14942914/Grid-%ED%8A%B8%EB%A6%AC%20%ED%99%94%EB%A9%B4%20%EB%AA%A9%EB%A1%9D.png" alt=""><figcaption></figcaption></figure>


# 팝업 UI 가이드

팝업은 작업 공간에서 데이터 조회 혹은 특정 작업을 처리하기 위해 사용합니다.

팝업창 내에서 추가로 생성되는 화면 구성은 작업 공간 화면의 영역과 동일합니다.

윈도우 팝업창은 업무 화면과 동일한 처리가 필요한 경우 \[저장]과 같은 액션 버튼을 제공하여 사용합니다.

***

## 팝업 사이즈 <a href="#undefined" id="undefined"></a>

윈도우 팝업의 사이즈는 다음 3가지를 지원합니다.

* 최소 사이즈 : 500px\* 600px
* 기본 사이즈 : 600px\*800px
* 최대 사이즈 : 화면 비율 90%
* 트리팝업 사이즈 : 380px\*560px

## 팝업 내 그리드 사용 <a href="#undefined" id="undefined"></a>

1. 팝업에서 조회 조건 입력 후 \[조회] 버튼 클릭 시 하단 그리드에 데이터를 나타냅니다.
2. 팝업 그리드 내 스크롤을 지원하며 화면 스크롤은 서치박스를 제외하고 고정 데이터 영역만 스크롤 할수 있도록 합니다.
3. 그리드에서 원하는 데이터 행을 더블 클릭 시 데이터를 부모창에서 보여주고 팝업은 닫힘 처리 됩니다.
4. 그리드에서 원하는 데이터 행을 선택 후 \[확인] 버튼을 클릭 시 데이터는 부모창에서 보여주고 팝업은 닫힘 처리 됩니다.
5. \[닫기] 버튼을 클릭하면 팝업이 닫힘 처리 됩니다.

## 팝업의 종류 <a href="#undefined" id="undefined"></a>

3가지 유형의 팝업을 제공하고 있습니다.

### 윈도우 팝업 <a href="#undefined" id="undefined"></a>

부모 창에서 호출 된 팝업 중 브라우저 밖에서 새로운 작업 공간이 생성되어 관련 작업을 처리 할 수 있는 팝업입니다. 작업 유형에 따라 Model 또는 Modaless 두 가지로 처리 할 수 있습니다. 사이즈는 최소 300px\*500px를 지원하며 최대 사이즈는 화면의 90%를 넘지 않아야 합니다.

![](https://tech.x2bee.com/download/attachments/15237142/UIUX%20-%20windows%20pop-up.png)

### 모달 팝업 <a href="#undefined" id="undefined"></a>

팝업창의 CUD작업과 같이 입력 값이 부모 화면에 영향을 미치는 경우에 사용하며, 팝업 화면 종료 후 부모 화면 제어가 가능한 팝업 유형입니다.

![](https://tech.x2bee.com/download/attachments/15237142/UIUX-Modal%20POP-UP.png)

### 레이어 팝업 <a href="#undefined" id="undefined"></a>

레이어 팝업은 단순 정보제공을 위해 사용하는 비 기능성 팝업 유형입니다.

<br>

<figure><img src="https://tech.x2bee.com/download/attachments/15237142/UIUX%20-%20modal%20pop-up.png" alt=""><figcaption></figcaption></figure>


# 지시문/오류/주석 작성 가이드

## 출력 형태 및 방법 <a href="#undefined" id="undefined"></a>

1. 다음과 같은 내용은 작업공간 화면 하단에 상태바에 처리 내용을 나타냅니다.
   * 메시지 팝업
   * 화면 해당 컴포넌트 인접 영역
   * 상태바(Satus Bar)
   * 단순 알림성 메시지
2. 모든 메시지는 서버에서 관리되는 정보를 기준으로 나타냅니다.
3. 팝업에도 상태바를 추가하여 동일한 방식으로 메시지 처리 상태를 나타냅니다.
4. 페이지 내 주석이 많을 경우 아이콘으로 표기하고 내용은 레이어 팝업으로 나타냅니다.

## 지시문 <a href="#undefined" id="undefined"></a>

1. 정보성 지시문은 확정형으로 서술합니다.
2. 청유, 권성 지시문은 사용자의 선택을 유도할 수 있는 문장으로 구성합니다.

## 도움말 <a href="#undefined" id="undefined"></a>

해당 항목에 대한 도움말을 아이콘 형태로 표기하고 마우스 오버시 레이어로 보여주며, 마우스가 해당 영역을 벗어나면 사라집니다.

## 안내문구 <a href="#undefined" id="undefined"></a>

1. 안내문구는 좌측하단에 배치합니다.
2. 문장은 서술형으로 마무리 합니다.
3. 문장앞에 '.' 블릿 부호를 사용해 표기 합니다.
4. 화면 전체에 해당되는 안내문구는 중요도에 따라 배치합니다.
5. 일반 안내 문구는 Normal로, 주요 안내문은 Bold로 적용합니다.
6. 그룹박스가 있을 때는 그룹박스 안쪽 좌측 하단에 작성합니다.

## 상황별 표기 예시 <a href="#undefined" id="undefined"></a>

<br>

<figure><img src="https://tech.x2bee.com/download/attachments/14550123/UIUX%20-%20%EC%A7%80%EC%8B%9C%EB%AC%B8_%EC%98%A4%EB%A5%98_%EC%A3%BC%EC%84%9D.png" alt=""><figcaption></figcaption></figure>


# 개발환경

## 개요

X2BEE의 개발을 위한 안정적인 개발 환경을 구축하는 방법에 대해 설명합니다.

***

## 사용 라이브러리 정보

| 명칭            | 버전       | 용도                                               |
| ------------- | -------- | ------------------------------------------------ |
| Next.js       | v16.2.11 | React 기반의 서버 사이드 렌더링(SSR) 및 정적 사이트 생성(SSG) 프레임워크 |
| React.js      | v19.2.3  | 사용자 인터페이스 구축을 위한 JavaScript 라이브러리                |
| Zustand       | v5.0.3   | 간단하고 직관적인 전역 상태 관리 라이브러리                         |
| TypeScript    | v6.0.3   | 정적 타입을 지원하는 JavaScript의 상위 집합 언어                 |
| TailwindCSS   | v4.1.16  | 유틸리티 퍼스트 CSS 프레임워크로 빠르고 일관된 UI 스타일링 지원           |
| ESLint        | v8.57.1  | 코드 품질 향상과 일관성 유지를 위한 JavaScript/TypeScript 린터    |
| Prettier      | v3.3.3   | 코드 자동 포매팅 도구로 스타일 일관성 유지에 사용                     |
| @mui/material | 6.0.1    | Material-UI를 이용한 UI 컴포넌트 라이브러리                   |

## 프로젝트 패키지 구조

{% stepper %}
{% step %}

### src/main/java

* 프레임워크 공통 클래스 패키지: 프로젝트 관련 설정, 예외처리, DB관련 속성, 보안, 유틸 클래스 포함
* API 관련 비즈니스 로직 클래스 패키지: Controller, Service, Dao, Entity 클래스

<div align="left"><figure><img src="/files/czB2WrQgibv64ZbtDOVi" alt="" width="426"><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

### src/main/resources

* 프레임워크 설정 패캐지: dev/local 별 datasource 설정, 로깅 설정, 기타 설정 파일
* API 관련 쿼리 패키지: 프로젝트 내에서 사용될 쿼리 파일
* View 템플릿 패키지: Front-end html 파일과 error html 파일
* 기타 설정 패키지: 프로젝트 전체 appliation.yml 파일 및 설정 파일

<div align="left"><figure><img src="/files/GladZQWPFSOQkSk4SgG0" alt="" width="439"><figcaption></figcaption></figure></div>
{% endstep %}
{% endstepper %}


# 로컬 개발 환경 설정하기

본 문서에서는 개발자 개발 환경 설정 방법에 대하여 기술합니다.

아래에서 로컬 개발환경 기본구조를 설명하고, 로컬개발환경 세팅 방법을 설명합니다.

***

## 로컬 개발환경

| **구분**       | **내용**                                                                      | **비고**                 |
| ------------ | --------------------------------------------------------------------------- | ---------------------- |
| **IDE Tool** | <p>IntelliJ IDEA 2024.1.2<br>Visual Studio Code 1.89<br>Eclipse 2024‑03</p> | 최신 버전 이용               |
| **JDK 버전**   | OpenJDK 21 (LTS)                                                            |                        |
| **Node 버전**  | Node.js 24.11 (LTS)                                                         |                        |
| **형상관리**     | GitLab                                                                      |                        |
| **WAS**      | Apache Tomcat 11.0.18                                                       | Java Servlet Container |
| **DBMS**     | PostgreSQL 15.3                                                             |                        |

### 주요 프레임워크(Framework)

* Spring Framework
  * spring-boot 4.0.3
  * spring-core 7.0.5
  * spring-data-jpa 4.0.3
  * tomcat-embed-core 11.0.18 (exclusion)

* Spring Cloud Framework

  * spring-cloud 2025.1.0

* Rust / Axum
  * axum 0.7
  * opensearch 2.3.0
  * sqlx 0.8
  * tokio-postgres 0.7

* Next.js Framework
  * next 16.2.3
  * next-intl 4.4.0
  * react 19.2.3
  * react-query 5.40.0
  * zustand 5.0.3

### Spring Framework 주요 라이브러리 (Library)

| **구분**                | **내용**                                                                                                                       | **비고** |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------ |
| **Logging**           | logback 1.5.6                                                                                                                |        |
| **Database**          | <p>mybatis 3.5.19<br>HikariCP 5.0.1<br>PostgreSQL 42.7.3</p>                                                                 |        |
| **Query 및 ORM**       | querydsl 7.0                                                                                                                 |        |
| **Cache**             | ehcache 3.11.1                                                                                                               |        |
| **인증 및 보안**           | jjwt 0.12.6                                                                                                                  |        |
| **Excel 처리**          | poi 5.2.5                                                                                                                    |        |
| **JSON 처리 (Jackson)** | <p>jackson-core 2.17.1<br>jackson-databind 2.17.1</p>                                                                        |        |
| **Apache Utils**      | <p>commons-lang3 (3.14.0)<br>commons-text 1.12.0<br>commons-io 2.16.1<br>commons-codec 1.17.0<br>commons-beanutils 1.9.4</p> |        |
| **편의성 라이브러리**         | Lombok 1.18.42                                                                                                               |        |
| **API 문서화**           | Springdoc 2.8.13 - Swagger3.0                                                                                                |        |

### Rust / Axum 주요 라이브러리 (Library)

| **구분**            | **내용**                                 | **비고** |
| ----------------- | -------------------------------------- | ------ |
| **Logging**       | log 0.4 / env\_logger 0.10             |        |
| **Database**      | sqlx 0.8 / tokio-postgres 0.7          |        |
| **Query 및 ORM**   | sqlx 0.8 (query)                       |        |
| **Cache**         | redis 0.32                             |        |
| **인증 및 보안**       | openssl 0.10 (TLS)                     |        |
| **통신 처리**         | reqwest 0.11                           |        |
| **검색엔진Database**  | opensearch 2.3.0                       |        |
| **인메모리 Database** | redis 0.32                             |        |
| **API 문서화**       | utoipa 4.2.0 / utoipa-swagger-ui 7.1.0 |        |

### Next.js Framework 주요 라이브러리 (Library)

| **구분**           | **내용**             | **비고** |
| ---------------- | ------------------ | ------ |
| **상태관리**         | zustand 4.5.2      |        |
| **데이터 조회 캐싱**    | react-query 5.40.0 |        |
| **다국어 처리**       | next-intl 4.4.0    |        |
| **반응형 슬라이드쇼 처리** | swiper 11.1.14     |        |
| **CSS**          | tailwindcss 4.1.16 |        |

## 로컬 개발환경 세팅

프로젝트 개발을 시작하기 위해 필요한 개발 도구 정보를 참고하여 설치해주세요.

| **구분** | **도구**                     | **다운로드**                                                                                     |
| ------ | -------------------------- | -------------------------------------------------------------------------------------------- |
| **개발** | IntelliJ IDEA              | [다운로드 바로가기 >](https://www.jetbrains.com/ko-kr/idea/download/)                                |
|        | Visual Studio Code         | [다운로드 바로가기 >](https://code.visualstudio.com/)                                                |
|        | Eclipse                    | [다운로드 바로가기 >](https://www.eclipse.org/downloads/)                                            |
| **필수** | git (Git Client 사용 가능)     | [다운로드 바로가기>](https://git-scm.com/)                                                           |
|        | SourceTree                 | [다운로드 바로가기>](https://www.sourcetreeapp.com/)                                                 |
|        | DBeaver                    | [다운로드 바로가기>](https://dbeaver.io/download/)                                                   |
|        | OpenJDK 21 (타사 OpenJDK 가능) | [다운로드 바로가기>](https://docs.aws.amazon.com/corretto/latest/corretto-21-ug/downloads-list.html) |
|        | Node.js 24                 | [다운로드 바로가기>](https://nodejs.org/en/download)                                                 |

* 프로젝트 목록

| 프로젝트명             | 설명                                |
| ----------------- | --------------------------------- |
| x2bee-common      | Spring 공통 기능 제공 프로젝트              |
| x2bee-gw          | Spring Cloud Gateway 프로젝트         |
| x2bee-bo          | 관리자 사이트 Next.js Front End 프로젝트    |
| x2bee-api-bo      | 관리자 API Spring 프로젝트               |
| x2bee-api-common  | 공통 API 및 외부 인터페이스 API Spring 프로젝트 |
| x2bee-api-display | 전시 API Spring 프로젝트                |
| x2bee-api-event   | 이벤트 API Spring 프로젝트               |
| x2bee-api-goods   | 상품 API Spring 프로젝트                |
| x2bee-api-order   | 주문 API Spring 프로젝트                |
| x2bee-api-member  | 회원 API Spring 프로젝트                |
| x2bee-batch-gddp  | 전시, 상품 Spring Batch 프로젝트          |
| x2bee-batch-mbod  | 회원, 주문 Spring Batch 프로젝트          |
| x2bee-api-search  | 검색엔진 프로젝트                         |
| x2bee-api-intf    | Interface API Spring 프로젝트         |
| x2bee-fo          | 사용자 사이트 Next.js Front End 프로젝트    |

***

### IntelliJ IDEA 설정 및 APP 실행하기

IntelliJ를 설치하고 실행합니다.

Git에서 소스코드를 복제하기 위해 “Git-Clone”을 실행합니다.

{% stepper %}
{% step %}

### 설치 및 실행

IntelliJ를 설치하고 실행합니다.

이미 설치되어 있다면 최신 버전으로 업데이트하세요.

<figure><img src="/files/XIJ94BK0IMFLhUoAZev3" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Git에서 소스 복제

1. IntelliJ에서 "Git-Clone" 기능을 사용하여 저장소를 복제합니다.
2. 복제한 전체 프로젝트 또는 개별 프로젝트를 선택합니다.
   {% endstep %}

{% step %}

### 의존성 리로드

프로젝트 선택 후 "Reload project"를 실행하여 Maven(또는 해당 빌드 툴)에서 의존성을 재설정 및 다운로드합니다.

<figure><img src="/files/yY22d9dQdjHtTAtiZ0mn" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### IntelliJ IDEA 설정 및 FO APP 실행하기 <a href="#intellij-idea-fo-app" id="intellij-idea-fo-app"></a>

&#x20;

![image-20240408-073437.png](https://tech.x2bee.com/download/attachments/124059658/image-20240408-073437.png?version=1\&modificationDate=1712561679254\&cacheVersion=1\&api=v2)

&#x20;

IntelliJ를 설치하고 실행합니다.

Git에서 소스코드를 복제하기 위해 “Git-Clone”을 실행합니다.

![image-20240408-072154.png](https://tech.x2bee.com/download/attachments/124059658/image-20240408-072154.png?version=1\&modificationDate=1712560917243\&cacheVersion=1\&api=v2)

package.json을 선택하고 "Run ‘npm install’"를 실행하여 npm에서 의존성을 재설정 및 다운로드합니다.

![image-20240408-072858.png](https://tech.x2bee.com/download/attachments/124059658/image-20240408-072858.png?version=1\&modificationDate=1712561340422\&cacheVersion=1\&api=v2)

npm 실행 목록에서 "start:local"을 선택하여 해당 프로젝트를 실행합니다.

***

### Visual Studio Code 설정 및 APP 실행하기 <a href="#visual-studio-code-app" id="visual-studio-code-app"></a>

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-025021.png?version=1\&modificationDate=1691463025015\&cacheVersion=1\&api=v2)

Visual Studio Code를 설치하고 실행합니다.

소스 코드를 복제하기 위해 "Source Control" 탭에서 "Clone Repository"를 실행합니다.

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-024941.png?version=1\&modificationDate=1691462985067\&cacheVersion=1\&api=v2)

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-024947.png?version=1\&modificationDate=1691462990954\&cacheVersion=1\&api=v2)

"Extensions" 탭에서 "java"를 검색하여 "Extension Pack for Java" 및 "Spring Boot Extension Pack"을 설치합니다.

&#x20;

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-025130.png?version=1\&modificationDate=1691463094383\&cacheVersion=1\&api=v2)

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-024947.png?version=1\&modificationDate=1691462990954\&cacheVersion=1\&api=v2)

해당 프로젝트의 실행 파일을 선택하고 “Run” 또는 “Debug”를 선택하여 해당 프로젝트를 실행합니다.

***

### Eclipse 설정 및 APP 실행하기 <a href="#eclipse-app" id="eclipse-app"></a>

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-025252.png?version=1\&modificationDate=1691463175687\&cacheVersion=1\&api=v2)

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-025259.png?version=1\&modificationDate=1691463182861\&cacheVersion=1\&api=v2)

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-025306.png?version=1\&modificationDate=1691463190134\&cacheVersion=1\&api=v2)

&#x20;

Eclipse를 설치하고 실행합니다.

"Open Perspective" 창에서 "Clone a Git repository"를 실행하여 Git Repository에서 소스 코드를 로컬 디렉토리에 복제합니다.

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-025321.png?version=1\&modificationDate=1691463205079\&cacheVersion=1\&api=v2)

[Lombok 다운로드](https://projectlombok.org/download)에서 "lombok.jar" 파일을 Eclipse 설치 경로에 다운로드합니다. 그 후, 설치된 폴더의 "eclipse.ini" 파일을 열어 "-javaagent:C:\설치경로\jee-2023-06\eclipse\lombok.jar"를 추가합니다.

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-025346.png?version=1\&modificationDate=1691463230659\&cacheVersion=1\&api=v2)

![](https://tech.x2bee.com/download/attachments/124059658/image-20230808-025358.png?version=1\&modificationDate=1691463241749\&cacheVersion=1\&api=v2)

실행할 프로젝트의 기동 파일을 선택하고 "Run" 또는 "Debug"를 선택하여 해당 프로젝트를 실행합니다.

&#x20;

<br>


# 공통 기능 구현 및 설정

## 개요

공통 기능들을 구현하고 설정하는 방법에 대해 설명합니다. 공통 기능들은 파일업로드, XSS 방지처리, HTTP Interface 구현하기, Masking 처리, API(Controller) 권한 설정 등이 있습니다. 각 기능별로 구현 방식, 사용 라이브러리, 설정 파일, 테스트 방법 등에 대해 상세하게 안내합니다.

***

## 문서 구성

<details>

<summary><a href="/pages/1f371527a4ee58dbefb32797388b6cdb1a1a9b81"><strong>XSS 방지 처리</strong> </a></summary>

사용자 입력을 안전하게 처리하는 방법에 대해서 설명합니다.S3에 파일 업로드의 구현하는 방법과 설정에 대한 가이드를 제공합니다.

</details>

<details>

<summary><a href="/pages/b9ad0b3b1ed93191cbfe07158fba52eb76166572"><strong>HTTP Interface 구현하기</strong> </a></summary>

서버 간의 효율적인 통신을 위해 HTTP 인터페이스를 구현하고 설정하는 방법에 대해 설명합니다.

</details>

<details>

<summary><a href="/pages/b929d3be32dab7ee6e838542b613424739d2a320"><strong>Masking 처리</strong> </a></summary>

민감한 정보나 개인정보를 보호하기 위한 중요한 기능으로 Masking 처리의 구현 방법과 설정에 대해 설명합니다.서버 간의 효율적인 통신을 위해 HTTP 인터페이스를 구현하고 설정하는 방법에 대해 설명합니다.

</details>

<details>

<summary><a href="/pages/e37a4689e10f9894d0c2fd1edad02f064edfb1c9"><strong>API(Controller) 권한 설정</strong> </a></summary>

PI 엔드포인트에 대한 권한 및 접근 제어를 설정하는 기능으로 어떻게 구현하고 관리하는지 설명합니다.

</details>

<details>

<summary><a href="/pages/AD9lDCmpddI5WAY92HgG#restapi"><strong>RestAPI 통신</strong></a></summary>

Spring의 REST API HTTP 통신에 대한 구현 및 X2BEE 환경에서의 적용 방식을 설명합니다.

</details>

<details>

<summary><a href="/pages/AD9lDCmpddI5WAY92HgG#url"><strong>단축 URL 가이드</strong></a></summary>

단축 URL을 생성하는 방법에 대해 다룹니다.

</details>

<details>

<summary><a href="/pages/AD9lDCmpddI5WAY92HgG#undefined-2"><strong>데이터그리​드</strong></a></summary>

목록 조회 및 데이터 관리를 효율적으로 처리하기 위한 MUI-X 데이터 그리드에 대해 설명합니다

</details>

<details>

<summary><a href="/pages/bfe7db1b3eb6a3afeda185d8c05bbbbafe9ad85d"><strong>파일업로드</strong> </a></summary>

S3에 파일 업로드의 구현하는 방법과 설정에 대한 가이드를 제공합니다.

</details>

{% hint style="info" %}
각 문서의 상세 내용은 X2BEE의 기술적인 세부 정보를 포함하고 있으며, 프로젝트 개발을 지원하기 위한 자세한 내용을 다루고 있습니다.
{% endhint %}


# XSS 방지 처리

다음은 XSS 방지 처리에 대한 설명입니다.

XSS 방지 기능의 경우 XssSanitizer 커스텀 어노테이션 및 XssProtectUtils Class 함수를 제공하고 있습니다.

***

## xss-rule.yml

(경로 : resources/config/xss-rule.yml)

다음은 예시 xss-rule.yml 설정입니다:

```yaml
# XSS Rule setting ###########################################
xss:
  direction: response #request(요청시에만), response(응답시에만), both(요청,응답 모두) XSS필터링이 적용됨
  allowElements: -> 허용할 Element 작성
    - a
    - img
    - div
    - ul
    - li
    - link
    - input
  allowAttributes: -> 허용할 Element의 허용할 Attribute 작성
    img:
      - alt
      - align
      - title
      - img
    div:
      - class
      - id
      - style
  allowUrls: -> 허용할 URL 목록과 각 URL에 허용할 Attribute 작성 (allowElements와 allowAttributes도 포함하여 적용됨)
    - url: /api/display/samples/xss2
      allowElements:
        - button
      allowAttributes:
    - url: /api/display/**/url -> ** 패턴 사용가능
      allowElements:
        - a
      allowAttributes:
        img:
          - alt
          - align
          - title
          - img
```

※ 해당 기능을 사용하기 위해서는 먼저 resources/config/xss-rule.yml 파일에 White List 기법으로 작성해줘야 됩니다.

* `direction` 속성은 필터링을 적용할 방향을 작성 합니다.
  * 세가지 옵션(`request`,`response`,`both`) 이 존재하며 request는 요청시에만, response는 응답시에만, both는 요청시와 응답시 모두 필터링이 적용됩니다.
* `allowElements`에는 허용할 Element를 작성 합니다.
* `allowAttributes`에는 허용할 Element에서 허용할 Attribute를 작성 합니다.
  * 만약에 allowElements에는 작성했지만 allowAttributes에는 작성하지 않았다면 해당 Element는 모두 허용하게 됩니다.
* `allowUrls`에는 특정 URL에서 별도로 허용해주고자 하는 Element와 Attribute를 명시합니다. (상위 `allowElements`와 `allowAttributes`의 속성들을 상속받습니다)
  * `url` 속성을 List 형태로 작성해야 하며 `url` 속성 하위에는 `allowElements`과 `allowAttributes` 를 작성합니다.
  * `url` 은 고정 URL과 URL-Pattern 모두 사용 가능합니다. 예시의 `/api/display/**/url` 와 같이 작성하면 /api/display/**test1**/url 또는 /api/display/**test1/test2/test3**/url 와 같은 URL들을 모두 필터링합니다.
  * `allowElements` 속성은 필수로 작성해야 하며 상위 `allowElements` 에 존재하는 Element의 `allowAttributes` 를 추가로 작성하고자 한다면 동일한 Element를 명시하고 `allowAttributes` 를 작성합니다.
  * `allowAttributes` 속성에 Attribute를 작성하면 해당 Attribute만 허용되며 빈칸으로 두면 `allowElements` 에 명시된 Element에서 사용가능한 모든 Attribute를 허용합니다.

## XssSanitizer 커스텀 어노테이션

예시 모델 클래스:

```java
@Alias("sampleRequest")
@Getter
@Setter
public class SampleRequest extends BaseCommonEntity {
    private Long id;
    private String name;
    @XssSanitizer
    private String description;
    private LocalDateTime localDateTime;
    private LocalDate localDate;
    private LocalTime localTime;
}
```

반환할 모델의 필드에 `@XssSanitizer` 어노테이션을 붙여주면 해당 필드는 MessageConverter에서 JSON Serialize할 경우에 XssProtectUtils Class의 `getHtmlSanitizer` 함수를 통하여 허용된 값들만 남기고 반환하게 됩니다.

<figure><img src="/files/u6nVklNTJriAEInNnK4P" alt=""><figcaption></figcaption></figure>

## 비즈니스 로직에서 XssProtectUtils 함수 사용

예시 컨트롤러 코드:

```java
private final XssProtectUtils xssProtectUtils;

@GetMapping("/xss2")
public ResponseEntity<Response> x22(@RequestBody @Valid Optional<SampleRequest> sampleRequest) {
    // 데이터와 함께
    log.info("sampleRequest: {}", sampleRequest.isPresent() ? sampleRequest.get() : "");
    String test = XssProtectUtils.getInstance().getHtmlSanitizer(sampleRequest.get().getDescription());
    String test2 = xssProtectUtils.getHtmlSanitizer(sampleRequest.get().getDescription());
    Response body = Response.builder()
        .payload(sampleRequest.get())
        .build();
    return ResponseEntity.ok().body(body);
}
```

일반 비즈니스 로직에서 다음과 같이 의존성 주입 및 싱글톤 방식으로 함수를 사용합니다.

<figure><img src="/files/mGVJvAW7pQmn7BIAa50s" alt=""><figcaption></figcaption></figure>

***


# HTTP Interface 구현하기

다음은 HTTP Interface에 대한 설명입니다.

Spring에서의 REST Api, Http 요청, Method Parameter, Return Values, HttpExchangeInterface X2BEE 공통 커스텀 어노테이션에 대해 설명합니다.

***

## 설명

Spring 6.0 (SpringBoot3.0) 부터는 REST API 통신으로 쓰이는 HTTP Interface가 추가 되었습니다. spring-cloud에 있는 FeignClient와 사용법이 유사합니다. spring-data-\*의 데이터 인터페이스와 비슷하게 인터페이스를 통해 REST API endpoint를 통해 데이터 통신을 하는 구조입니다.

Spring에서 REST Api 지원 역사는 아래와 같습니다.

* Spring 3.0 → RestTemplate (앞으로 deprecated될 예정이므로 사용X)
* Spring 5.0 → WebFlux의 WebClient
* Spring 6.0 → HTTP Interface (내부적으로 WebClient 사용)

FeignClient와 다른 점은 각 endpoint의 BASE URL을 WebClient에 선언하고 해당 인터페이스가 Bean으로 등록되어야 하는 점입니다. 이는 Spring에서 명시적으로 endpoint를 집중해서 관리하려는 의미이기도 합니다.

역자 주) FeignClient의 편리함에 익숙해져 있다면 다소 불편하게 느껴질 수도 있습니다.

아래는 WebClient와 HttpServiceProxyFactory를 사용해 인터페이스를 Bean으로 등록하는 예시입니다.

{% code title="HttpConfig.java" %}

```java
@Configuration
public class HttpConfig {
    @Bean
    RepositoryService repositoryService() {
        WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
        RepositoryService service = factory.createClient(RepositoryService.class);
        return service;
    }
}
```

{% endcode %}

위 코드에 대한 RepositoryService 코드는 아래와 같습니다.

{% code title="RepositoryService.java" %}

```java
public interface RepositoryService {
    @GetExchange("/posts")
    List<Post> getPosts();
}
```

{% endcode %}

여러 개의 REST Api endpoint를 관리하기 위해서는 아래와 같은 모양이 됩니다.

{% code title="HttpConfig (multiple services).java" %}

```java
@Configuration
public class HttpConfig {
    @Bean
    public WebClient client() {
        return WebClient.builder().baseUrl("http://localhost:8080").build();
    }

    @Bean
    public HttpServiceProxyFactory httpServiceProxyFactory(WebClient client) {
        return HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
    }

    @Bean
    public ProductService productService(HttpServiceProxyFactory httpServiceProxyFactory) {
        return httpServiceProxyFactory.createClient(ProductService.class);
    }

    @Bean
    public PaymentService paymentService(HttpServiceProxyFactory httpServiceProxyFactory) {
        return httpServiceProxyFactory.createClient(PaymentService.class);
    }
}
```

{% endcode %}

서비스 인터페이스 예시:

{% code title="PaymentService.java" %}

```java
public interface PaymentService {
    @PutExchange("/payment")
    void payAmount(@RequestParam BigDecimal amount);
}
```

{% endcode %}

{% code title="ProductService.java" %}

```java
public interface ProductService {
    @PutExchange("/product/decrease/{productId}")
    void decreaseQuantity(@PathVariable String productId, @RequestParam int quantity);
}
```

{% endcode %}

## Http 요청

| 종류              | 설명                   |
| --------------- | -------------------- |
| @GetExchange    | HTTP GET requests    |
| @PostExchange   | HTTP POST requests   |
| @PutExchange    | HTTP PUT requests    |
| @PatchExchange  | HTTP PATCH requests  |
| @DelectExchange | HTTP DELETE requests |

## Method Parameter

| 종류             | 설명                                                                                                                                                                                                                                           |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| URI            | annotation의 url을 덮어쓰고, 동적으로 요청의 URL을 설정한다.                                                                                                                                                                                                   |
| HttpMethod     | annotation의 method를 덮어쓰고, 동적으로 요청의 HTTP 메서드를 설정한다.                                                                                                                                                                                           |
| @RequestHeader | 요청에 헤더를 추가한다. Map\<String, ?> 또는 여러값인 경우 MultiValueMap\<String, ?>, Collection\<?>, 단일 값을 사용한다. 문자열이 아닌 경우에는 타입변환이 일어난다.                                                                                                                     |
| @PathVariable  | URL에 포함된 path variable을 추가한다. 단일 값을 쓰거나, 여러 인자가인 경우 Map\<String, ?>을 사용할 수 있다. 문자열이 아닌 경우에는 타입변환이 일어난다.                                                                                                                                      |
| @RequestBody   | serialize 될 객체 또는 Mono, Flux 같은 Publisher 또는 ReactiveAdapterRegistry에 의해 지원하는 비동기 타입의 어느 값을 body 제공한다.                                                                                                                                       |
| @RequestParam  | 요청에 파라미터를 추가한다. Map\<String, ?> 또는 여러값인 경우 MultiValueMap\<String, ?>, Collection\<?>, 단일 값을 사용한다. 문자열이 아닌 경우에는 타입변환이 일어난다. Content-type이 application/x-www-form-urlencoded로 설정되어 있으면, 요청의 파라미터는 body로 encode 된다. 그 외의 경우는 url 쿼리 파라미터로 추가된다. |
| @RequestPart   | 문자열, Resource(org.springframework.http.codec.multipart.FilePart), Object(JSON 같은 값으로 encode 될 엔티티), HttpEntity(part의 콘텐츠와 헤더), Part(org.springframework.http.codec.multipart), 위의 값들을 가진 Publisher를 멀티파트의 한 파트를 추가한다.                        |
| @CookieValue   | 쿠키 값을 추가한다. Map\<String, ?> 또는 여러값인 경우 MultiValueMap\<String, ?>, Collection\<?>, 단일 값을 사용한다. 문자열이 아닌 경우에는 타입변환이 일어난다.                                                                                                                       |

## Return Values

| 종류                                    | 설명                                                                                                  |
| ------------------------------------- | --------------------------------------------------------------------------------------------------- |
| void, Mono                            | 요청을 수행하고, 응답의 콘텐츠를 버린다.                                                                             |
| HttpHeaders, Mono                     | 요청을 수행하고, 응답의 콘텐츠를 버리고 응답의 헤더를 리턴한다.                                                                |
| , Mono                                | 요청을 수행해고, 응답을 주어진 리턴타입으로 decode 한다.                                                                 |
| , Flux                                | 요청을 수행하고, 응답을 주어진 리턴타입의 스트림으로 decode 한다.                                                            |
| ResponseEntity, Mono\<ResponseEntity> | 요청을 수행하고, 응답의 콘텐츠를 버린다. 그리고 ResponseEntity를 상태와 헤더와 함께 리턴한다.                                        |
| ResponseEntity, Mono\<ResponseEntity> | 요청을 수행하고, 응답을 주어진 리턴타입으로 decode한다. 그리고 ResponseEntity를 상태와 헤더, 그리고 decode 된 body와 함께 리턴한다.          |
| Mono\<ResponseEntity\<Flux>>          | 요청을 수행하고, 응답을 주어진 리턴타입의 스트림으로 decode한다. 그리고 ResponseEntity를 상태와 헤더, 그리고 decode 된 body 스트림과 함께 리턴한다. |

## HttpExchangeInterface (X2BEE 공통 커스텀 어노테이션)

위 설명에서와 같이 REST Api endpoint를 관리하기 위해서는 WebClient 설정을 Bean으로 등록하여 사용하여야 합니다. X2BEE에서는 해당 endpoint 관리를 편리하게 하기 위해서 HttpExchangeInterface 커스텀 어노테이션을 지원합니다.

HttpExchangeInterface.java (개요)

```java
/**
 * Http Interface 용 커스텀 어노테이션
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpExchangeInterface {
    String baseUrl() default "";
    String defaultPath() default "";
    int responseTimeoutSeconds() default 0;
    int connectionTimeoutSeconds() default 10;
    boolean enableTokenAuth() default true;
    boolean useMemberToken() default false;
}
```

해당 어노테이션은 다음과 같은 항목들을 제공합니다.

| 종류                       | 설명                                                                                                                                                                                                     |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| baseUrl                  | Api endpoint url 설정값으로 Bean으로 등록하기 위하여 필수값임. 참고로 그냥 String값을 사용하면 해당값이 그대로 사용되고 ${app.apiUrl.event}와 같은 형태로 사용되면, application.yml에 등록된 환경변수값을 `env`.getProperty("app.apiUrl.event"); 같은 형태로 가져와 사용합니다. |
| defaultPath              | 해당 Api 서버의 ContextPath값으로 해당값이 있을 경우 baseUrl과 합쳐서 사용함. 해당값 역시 baseUrl과 동일하게 ${default.path} 형태를 제공합니다.                                                                                                 |
| responseTimeoutSeconds   | Http Client의 Response Timeout 설정값으로 기본값은 0 입니다.                                                                                                                                                        |
| connectionTimeoutSeconds | Http Client의 Connection Timeout 설정값으로 기본값은 10 입니다.                                                                                                                                                     |
| enableTokenAuth          | 헤더값에 Auth 토큰값을 포함할지 여부로서 기본값은 true입니다. 해당값은 기존 공통 RestApi Class의 enableTokenAuth값 함수를 그대로 가져왔습니다.                                                                                                      |
| useMemberToken           | 헤더값에 로그인 사용자 토큰값을 포함할지 여부로서 기본값은 false입니다. 해당 옵션은 enableTokenAuth값이 true일때만 동작합니다. 기존 공통 RestApi Class의 useMemberToken값 함수를 그대로 가져왔습니다.                                                                |

예시 인터페이스 사용:

{% code title="HttpEventSampleService.java" %}

```java
@HttpExchangeInterface(baseUrl = "${app.apiUrl.event}", defaultPath = "/api/event")
public interface HttpEventSampleService {
    @GetExchange("/samples/search2")
    ResponseEntity<Response> searchSamples2(@RequestHeader Map<String, Object> requestHeader,
                                           @RequestBody SampleRequest sampleRequest);
}
```

{% endcode %}

{% code title="HttpGoodsSampleService.java" %}

```java
@HttpExchangeInterface(baseUrl = "${app.apiUrl.goods}", defaultPath = "/api/goods", enableTokenAuth = false)
public interface HttpGoodsSampleService {
    @GetExchange("/samples/search2")
    ResponseEntity<Response> searchSamples2(@RequestBody SampleRequest sampleRequest);
}
```

{% endcode %}

위 예제와 같이 interface class에 @HttpExchangeInterface 어노테이션을 붙여줍니다.

해당 어노테이션이 있을 경우 스프링 시작 시점에 모든 패키지의 Class 파일을 검사하여 @HttpExchangeInterface이 붙은 Class 파일을 찾아 해당 어노테이션의 설정값으로 WebClient를 생성하고 Bean으로 등록해 줍니다.

사용 예시:

{% code title="HttpSample.java" %}

```java
public ResponseEntity<Response> http1SearchSamples1() {
    Map<String, Object> requestHeader = new HashMap<>();
    requestHeader.put("test1", "kang1");
    requestHeader.put("test2", "kang2");

    SampleRequest body = new SampleRequest();
    body.setName("test");

    ResponseEntity<Response> result = httpEventSampleService.searchSamples2(requestHeader, body);
    return result;
}
```

{% endcode %}

실행 시 enableTokenAuth 값이 true일 경우 헤더에 Authorization 값 또한 정상적으로 포함되어 요청이 전송되는 것을 확인할 수 있습니다.

<figure><img src="/files/i8vyi0ljMPDO2C78OoM7" alt=""><figcaption></figcaption></figure>

***


# Masking처리

다음은 Masking 처리에 대한 설명입니다.

변경점

* Masking 처리를 DAO ↔︎ DBMS에서 MessageConverter (응답값 반환 처리 모듈)로 변경하였습니다.
* 기존에 사용되었던 Mybatis 인터셉터 모듈(`<plugin interceptor="com.x2bee.common.base.masking.MybatisMaskingInterceptor"/>`)은 모두 삭제되었습니다.
* 이전처럼 DAO단에서 변환을 처리하면 서비스 레이어에서 비즈니스 로직을 처리할 때 불편하므로, Controller에서 응답값을 반환할 때 MessageConverter에서 마스킹을 적용하도록 변경하였습니다.

***

## 설명

변경된 방식은 Controller에서 응답값을 반환할 때, 응답값을 처리하는 MessageConverter에서 Masking 처리합니다.

예시 DTO:

{% code title="SampleResponse.java" %}

```java
public class SampleResponse {
    private Long id;

    @MaskString(type = MaskingType.NAME_EN)
    private String name;

    private String description;
}
```

{% endcode %}

사용 방법은 기존과 동일하며, @MaskString 애노테이션을 그대로 사용합니다.

***

## 커스텀 MaskingUtils 적용

MaskingUtils 기능은 기본적으로 common 프로젝트에 있는 MaskingUtils 클래스를 사용합니다.

Apibo처럼 기본 MaskingUtils를 변경해서 적용해야 할 경우, 아래 절차를 따르세요.

1. 커스텀 MaskingUtils 클래스 생성

예:

{% code title="BoApiMaskingUtils.java" %}

```
```

{% endcode %}

```java
@Component
public class BoApiMaskingUtils extends MaskingUtils {
    public String getValue(String value, MaskingType type) {
        return getValue(value, type, true);
    }
}
```

위 예시처럼 공통 MaskingUtils 클래스를 상속받고, 마스킹을 실행하는 함수인 `getValue`를 오버라이딩하여 재정의합니다.

2. 설정값에 커스텀 MaskingUtils bean name 지정

application.yml 예:

{% code title="application.yml" %}

```
```

{% endcode %}

\`\`\`yaml masking: utils: bean: boApiMaskingUtils \`\`\` application.yml 파일에서 \`masking.utils.bean\` 설정에 해당 bean name을 정의합니다.


# API(Controller) 권한 설정

다음은 API 권한 설정에 대한 설명입니다.

API 권한 설정 시 Security Config 설정과 Controller에서 API 작성 권한 설정, 특정 역할이 설정된 권한 API 호출 방법에 대해 설명합니다.

***

## Security Config 설정

| **프로젝트**     | **SecurityConfig 내용**                                                                                                                                                                                                                                     |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| x2bee-bo     | <p>- Constants.getGuestUris()에 설정된 URI의 경우 permitAll() 설정으로 전체 허용 - 로그인 페이지, 오류 페이지, 샘플페이지 등<br>- GuestUris를 제외한 모든 API는 authorize.anyRequest().authenticated() 설정으로 스프링 시큐리티 인증 필요<br>- BO에서 관리자 로그인을 성공할 경우 ROLE\_ADMIN 권한을 가지게 됨</p>                   |
| x2bee-api-bo | - 모든 API는 authorize.anyRequest().hasAnyRole("SERVICE", "ADMIN") 설정으로 스프링 시큐리티 인증이 필요하며 ROLE\_SERVICE, ROLE\_ADMIN의 역할이 있어야 인가됨                                                                                                                            |
| x2bee-api-업무 | <p>- BO를 제외한 모든 API 프로젝트들은 @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true) 해당 옵션이 설정되어서 중앙의 securityFilterChain에서 관리하지 않고 개별 Controller Method에 Security 설정을 추가함<br>- FO, MO 쪽에서 API-MEMBER를 통해 로그인을 성공할 경우 ROLE\_MEMBER 권한을 가지게 됨</p> |

## Controller에서 API 작성시 권한 설정

※ 해당 내용은 'api-{업무}'에서만 활용되며, api-bo의 경우에는 중앙의 securityFilterChain에서 모든 API에 대하여 ROLE\_SERVICE 및 ROLE\_ADMIN 역할만 인가되도록 설정되어 있습니다.

| **권한**                               | **내용**                                                                                                                                                                                                                                                                  |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 권한없음                                 | <p>@GetMapping("/search4")<br>public Response searchSamples4() {<br>return Response.builder().payload("ROLE\_NONE").build();<br>}</p><p>모든 호출이 인가됨.</p>                                                                                                                 |
| ROLE\_SERVICE 역할 인가 허용               | <p>@Secured("ROLE\_SERVICE")<br>@GetMapping("/search1")<br>public Response searchSamples1() {<br>return Response.builder().payload("ROLE\_SERVICE").build();<br>}</p><p>ROLE\_SERVICE 역할을 가진 인증자만 인가됨.</p>                                                              |
| ROLE\_MEMBER 역할 인가 허용                | <p>@Secured("ROLE\_MEMBER")<br>@GetMapping("/search2")<br>public Response searchSamples2() {<br>return Response.builder().payload("ROLE\_MEMBER").build();<br>}</p><p>ROLE\_MEMBER 역할을 가진 인증자만 인가됨.</p>                                                                 |
| ROLE\_SERVICE, ROLE\_MEMBER 역할 인가 허용 | <p>@PreAuthorize("hasRole('ROLE\_SERVICE') or hasRole('ROLE\_MEMBER')") @GetMapping("/search3") public Response searchSamples3() { return Response.builder().payload("ROLE\_SERVICE, ROLE\_MEMBER").build(); }</p><p>ROLE\_SERVICE 또는 ROLE\_MEMBER 역할을 가진 인증자만 인가됨.</p> |

## 특정 역할이 설정된 권한 API 호출 방법

<table data-header-hidden><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td><strong>권한</strong></td><td><strong>함수</strong></td><td><strong>내용</strong></td></tr><tr><td>ROLE_SERVICE</td><td>- RestApiUtil.get<br>- RestApiUtil.post<br>- RestApiUtil.put<br>- RestApiUtil.patch<br>- RestApiUtil.delete</td><td><p></p><pre><code>@Value("${app.apiUrl.goods}")
private String goodsApiUrl;

private final RestApiUtil restApiUtil;

@GetMapping("/test1")
public Response\<String> test1() {
Response\<String> result = restApiUtil.get(goodsApiUrl+ "/api/goods/samples/search1"
, null, new ParameterizedTypeReference\<Response\<String>>() {});

```
return result;
```

} </code></pre><p>-> Secured가 붙지 않은 일반 호출 함수로 API를 실행할 경우 내부적으로 ROLE\_SERVICE 역할을 가진 JWT 토큰을 생성하여 Authorization 헤더에 토큰을 저장하여 전달함. 전달받는 API의 Token Filter 클래스에서는 해당 JWT 토큰값을 확인하여 ROLE\_SERVICE 역할을 가진 Security 인증을 생성하여 ROLE\_SERVICE가 붙은 API를 인가시킴.</p></td></tr><tr><td>ROLE\_MEMBER</td><td>- RestApiUtil.getSecured<br>- RestApiUtil.postSecured<br>- RestApiUtil.putSecured<br>- RestApiUtil.patchSecured<br>- RestApiUtil.deleteSecured</td><td><p></p><pre><code>@Value("${app.apiUrl.goods}")
private String goodsApiUrl;

private final RestApiUtil restApiUtil;

@GetMapping("/test2")
public Response\<String> test2() {
Response\<String> result = restApiUtil.getSecured(goodsApiUrl+ "/api/goods/samples/search2"
, null, new ParameterizedTypeReference\<Response\<String>>() {});

```
return result;
```

} </code></pre><p></p><p>> Secured가 붙은 호출 함수로 API를 실행할 경우 내부적으로 Security 인증 객체를 체크하여 해당 객체에서 mbrNo 값을 가져와 해당 값과 ROLE\_MEMBER 역할을 가진 JWT 토큰을 생성하여 Authorization 헤더에 토큰을 저장하여 전달함. 전달받는 API의 Token Filter 클래스에서는 해당 JWT 토큰값을 확인하여 해당 mbrNo값으로 실제 인증된 사용자인지 체크함. 인증된 사용자일 경우 해당 조회한 사용자 객체로 ROLE\_MEMBER 역할을 가진 Security 인증을 생성하여 ROLE\_MEMBER가 붙은 API를 인가시킴.</p></td></tr></tbody></table>

{% hint style="warning" %}
주의사항 → Secured 함수를 호출할 경우 로그인이 되어 있지 않다면 Security 인증 객체에서 mbrNo 값을 가져올 수 없어 권한 오류가 발생합니다. 해당 함수를 사용하여 호출할 경우에는 호출하는 쪽에서 로그인이 되어 있어야 합니다.
{% endhint %}


# RestAPI 통신

본 문서는 Spring의 REST API HTTP 통신에 대한 구현 및 X2BEE 환경에서의 적용 방식을 설명합니다.

***

## Spring REST API 지원의 변화

Spring에서 REST API 통신을 지원하는 방식은 버전에 따라 다음과 같이 진화했습니다:

* Spring 3.0
  * RestTemplate: REST API 호출을 위한 표준으로 사용되었으나, Spring 6.0부터는 사용 중단 예정입니다.
  * 주의: RestTemplate은 신규 프로젝트에서는 사용하지 않는 것이 권장됩니다.
* Spring 5.0
  * WebClient: 비동기 및 블로킹 통신 모두를 지원하며, WebFlux 모듈에 포함되어 있습니다.
  * X2BEE에서는 WebClient를 내부 통신 모듈로 활용 중입니다.
* Spring 6.0
  * HTTP Interface:
    * WebClient를 내부적으로 사용하여 REST API와의 통신을 인터페이스로 정의합니다.
    * FeignClient와 유사하지만, 명시적으로 WebClient와 Bean 등록이 필요합니다.
    * X2BEE에서는 이와 유사한 RestApiInterface 어노테이션을 구현하여 외부 통신에 활용하고 있습니다.

{% hint style="info" %}
참고: RestTemplate은 과거 표준이었으나, 현재는 WebClient / HTTP Interface 기반 사용이 권장됩니다.
{% endhint %}

## HTTP Interface 설정

HTTP Interface는 WebClient를 사용해 REST API의 엔드포인트를 설정하고 이를 Spring Bean으로 등록합니다.

설정 예제:

{% code title="HttpConfig.java" %}

```java
@Configuration
public class HttpConfig {
    @Bean
    RepositoryService repositoryService() {
        WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
        return factory.createClient(RepositoryService.class);
    }
}
```

{% endcode %}

* WebClient.create: Base URL 설정.
* HttpServiceProxyFactory: HTTP 인터페이스 Bean 생성을 위한 팩토리 클래스

## X2BEE `RestApiInterface` (공통 커스텀 어노테이션)

X2BEE는 REST API 엔드포인트 관리의 편리성을 위해 `@RestApiInterface`라는 커스텀 어노테이션을 제공합니다.

어노테이션 정의 예제:

{% code title="RestApiInterface.java" %}

```java
/**
 * Rest Api Interface 용 커스텀 어노테이션
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestApiInterface {
    String baseUrl() default "";
    String defaultPath() default "";
    int connectionTimeoutSeconds() default 10;
    int readTimeoutSeconds() default 60;
    int writeTimeoutSeconds() default 60;
    boolean enableTokenAuth() default true;
    boolean useMemberToken() default false;
}
```

{% endcode %}

### 주요 속성

| 속성명                        | 설명                                                     | 기본값   |
| -------------------------- | ------------------------------------------------------ | ----- |
| `baseUrl`                  | API 엔드포인트 URL (필수). 환경변수와 연동 가능: `${app.apiUrl.event}` | -     |
| `defaultPath`              | 기본 경로. `baseUrl`와 결합하여 최종 URL 형성.                      | -     |
| `connectionTimeoutSeconds` | 연결 대기 시간 설정 (초).                                       | 10    |
| `readTimeoutSeconds`       | 응답 대기 시간 설정 (초).                                       | 60    |
| `writeTimeoutSeconds`      | 요청 데이터 전송 시간 설정 (초).                                   | 60    |
| `enableTokenAuth`          | 인증 토큰을 헤더에 포함 여부 설정.                                   | true  |
| `useMemberToken`           | 사용자 인증 토큰 포함 여부 설정 (`enableTokenAuth=true`일 때 동작).     | false |

## HTTP Interface 구현

REST API Interface 예제:

{% code title="TestInterface.java" %}

```java
@RestApiInterface(baseUrl = "${app.apiUrl.common}", defaultPath = "/api/common", enableTokenAuth = false, useMemberToken = false)
public interface TestInterface {
    @Headers({"Content-Type: application/json; charset=utf-8"})
    @GET("/interface/test1")
    Call<Response> commonTest1();

    @Headers({"Content-Type: application/json; charset=utf-8"})
    @GET("/interface/test2")
    Call<Response> commonTest2();
}
```

{% endcode %}

* `@RestApiInterface`: Bean 등록 시 인터페이스를 자동 검색 및 설정.
* `@Headers`: API 호출 시 필요한 헤더 값 설정.

HTTP 요청 호출 예 (HTTPSample.java):

{% code title="HTTPSample.java" %}

```java
private final TestInterface testInterfaceService;

public ResponseEntity<Response> http1SearchSamples1() {
    ResponseEntity<Response> result1 = Http.requestEntitySync(testInterfaceService.commonTest1(), new TypeReference<Response>() {});
    // -> 동기식 ResponseEntity 반환

    Response<String> result2 = Http.requestSync(testInterfaceService.commonTest1(), new TypeReference<Response>() {});
    // -> 동기식 payload 반환

    Mono<ResponseEntity<Response>> result3 = Http.requestEntityAsync(testInterfaceService.commonTest1(), new TypeReference<Response>() {});
    // -> 비동기식 ResponseEntity 반환
    ResponseEntity<Response> result33 = result3.block();
    // -> 비동기적인 작업의 결과를 동기적으로 변환

    Mono<Response> result4 = Http.requestAsync(testInterfaceService.commonTest1(), new TypeReference<Response>() {});
    // -> 비동기식 payload 반환
    Response result44 = result4.block();
    // -> 비동기적인 작업의 결과를 동기적으로 변환

    return result1;
}
```

{% endcode %}

## RestApiUtil (X2BEE 공통 Http 통신)

X2BEE 내부 통신 모듈인 `RestApiUtil`은 REST API 호출을 간소화합니다. 해당 유틸의 경우 내부통신에서만 사용되기 때문에 반환값이 Response으로 고정되어 있습니다.

JAVA 사용 예:

{% code title="RestApiUtil 사용 예.java" %}

```java
@Value("${app.apiUrl.common}")
private String commonApiUrl;

private final RestApiUtil restApiUtil;

public Response<String> http1SearchSamples1() {
    return restApiUtil.get(
        commonApiUrl + "/interface/test1",
        null,
        new ParameterizedTypeReference<Response<String>>() {}
    );
}
```

{% endcode %}

## React 연동 예제

React에서 HTTP 요청 헬퍼를 구성하여 간편하게 REST API를 호출할 수 있습니다.

React 사용 예:

{% code title="RestApi.ts" %}

```typescript
const RestApi = (() => {
    class RestApi {
        private static instance: RestApi // Singleton 패턴 구현
        static getInstance() {
            if (RestApi.instance) {
                return this.instance
            }
            this.instance = new RestApi()
            return this.instance
        }

        // HTTP 요청 메서드
        async http(
            url: string,
            method: string,
            options?: RequestOptions
        ): Promise<ResponseEntity | null> {
            // Method 분기 처리 ...
            return this.fetch(url, method, options)
        }

        // 내부 fetch 메서드
        private async fetch(
            url: string,
            method: string,
            options?: RequestOptions,
            accessToken: string = '',
            isAuth: boolean = true
        ): Promise<ResponseEntity | null> {
            ...
            switch (method) {
                case 'GET':
                    response = await restApiUtil
                        .get(url, options)
                        .then((response) => response)
                        .catch((errorResponse) => errorResponse)
                    break
                ...
            }
            return response
        }
    }
})()

// GET 요청을 위한 헬퍼 함수
function httpGet<T = object>(
    url: string,
    options?: RequestOptions
): Promise<T> {
    return RestApi.getInstance().http(url, 'GET', options) as Promise<T>
}

// restApi 객체 내보내기
export const restApi = {
    get: httpGet
}
```

{% endcode %}

API 호출 예:

{% code title="sampleApi.ts" %}

```typescript
import { restApi } from './RestApi'

export const fetchSampleList = async (
    params: SampleSchemaType
): Promise<SampleApiResponse> => {
    const response = (await restApi.get(
        '/api/common/interface/test1',
        { params }
    )) as GridResponse<SampleResponseDetail[]>
    return response.payload
}
```

{% endcode %}

***

##


# 단축 URL 가이드

이 문서는 단축 URL을 생성하는 방법에 대해 다룹니다.

{% stepper %}
{% step %}

### Config 설정 방법 및 확인

application.yml 예시:

```yaml
shortUrl:
  clientId: fguXF8_4cJHrRgDEIQCJ
  secretKey: dEs5wPqo93
```

{% hint style="info" %}
`clientId`, `secretKey` 속성들은 네이버 개발자 센터에서 발급받아 작성합니다.
{% endhint %}
{% endstep %}

{% step %}

### 각 업무단 작성 방법 (샘플 및 설명)

#### Sample 소스 (예시)

```java
... private final ShortUrl shortUrl;

public ShortUrlResponse shortUrl(String url) throws Exception {
    ShortUrlResponse shortUrlResponse = shortUrl.getShortUrl(url);
    return shortUrlResponse;
}
...
```

단축할 url 정보를 String으로 받아 `getShortUrl` 메소드에 파라미터로 넣고 호출하면 아래 테이블과 같이 값이 반환 됩니다.

| 속성               | 타입     | 설명                     |
| ---------------- | ------ | ---------------------- |
| message          | string | 오류 메시지. 응답에 성공하면 ok 반환 |
| code             | string | HTTP 상태 코드             |
| result.hash      | string | 단축 URL의 해시 정보          |
| result.url       | string | 단축된 URL                |
| result.orgUrl    | string | 원본 URL                 |
| {% endstep %}    |        |                        |
| {% endstepper %} |        |                        |


# 데이터 그리드 (Mui-X)

X2BEE BO(BackOffice) 템플릿은 목록 조회 및 데이터 관리(입력/수정/삭제)를 효율적으로 처리하기 위해 [MUI-X 데이터 그리드](https://mui.com/x/react-data-grid/)를 활용합니다. 이 템플릿은 BO 프로그램 전반에 걸쳐 폭넓게 사용되며, MUI를 확장하여 추가 기능을 제공함으로써 개발 편의성을 크게 향상시킵니다.

***

## X2beeDataGrid

{% stepper %}
{% step %}

### 데이터 그리드 파일 구조

* 파일 위치는 `/src/lib/x2bee-data-grid/*`입니다.

| **파일명**                          | **설명**                                                                    |
| -------------------------------- | ------------------------------------------------------------------------- |
| **x2bee-data-grid.tsx**          | 화면에 그려지는 그리드 컴포넌트입니다.                                                     |
| **x2bee-data-grid-utils.ts**     | 그리드 컴포넌트에서 사용되는 함수들로, 다회 사용되거나 분리가능하고 상대적으로 긴 코드들을 컴포넌트에서 분리해 둔 유틸 파일입니다. |
| **x2bee-data-grid-constants.ts** | 그리드에서 사용되는 상수들을 관리하는 파일입니다.                                               |
| **x2bee-data-grid-types.ts**     | 그리드에서 사용되는 타입들을 관리하는 파일입니다.                                               |
| **x2bee-grid-date-picker.tsx**   | 그리드에서 사용되는 date picker 관련 파일입니다.                                          |
| **use-x2bee-data-grid.ts**       | 그리드에 관련된 hook이 선언된 파일입니다.                                                 |
| **x2bee-data-grid-context.tsx**  | 그리드를 감싼 컴포넌트로 dataRef를 주입해줍니다.                                            |
| {% endstep %}                    |                                                                           |

{% step %}

### 데이터 그리드 컴포넌트 제공 옵션

* [MUI 그리드 옵션](https://mui.com/x/api/data-grid/data-grid-premium/)에서 MUI 제공 옵션에 대해 자세하게 확인할 수 있습니다.
* X2beeDataGrid 컴포넌트에서 추가적으로 제공하는 옵션은 다음과 같습니다.

| **옵션**          | **설명**                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| dataRef         | MUI에서 제공되는 API(apiRef) 외에 추가적으로 제공되는 함수들을 포함한 ref 객체입니다. [소스](https://gitlab.x2bee.com/team-project/ec3-1/bo-mui/-/blob/main/src/lib/x2bee-data-grid/x2bee-data-grid-types.ts) 페이지의 X2beeDataGridDataRefType에서 제공되는 기능들을 확인할 수 있습니다. 대표적으로 그리드에서 관리되는 CUD의 row state별로 row 리스트를 가져올 수 있으며, 버튼의 행 관리 기능도 함수로 제공됩니다.                                                                                                                                        |
| hideDeleteRow   | Row 삭제 시 그리드에서 노출 여부를 설정합니다. 기본값은 false입니다.                                                                                                                                                                                                                                                                                                                                                                                                               |
| enableRowState  | Row의 상태(CUD)를 표시할지 여부를 설정합니다. 기본값은 false입니다.                                                                                                                                                                                                                                                                                                                                                                                                              |
| fetchSave       | Row의 저장 버튼에 대한 콜백 함수입니다. 선언 시 저장 버튼이 노출됩니다.                                                                                                                                                                                                                                                                                                                                                                                                               |
| paginationProps | 페이지네이션을 구현하기 위해, 필요하지 않은 경우 MUI 데이터 그리드의 기본 속성 `rows`를 간단히 사용할 수 있습니다. 기본값으로 최소한 빈 배열을 제공하는 것이 필수적입니다. `paginationProps`가 없으면 `rows`는 필수로 지정되어야 합니다. 이 접근 방식은 MUI의 페이지네이션 구현에 의존하지 않고 X2beeDataGrid 내에서 모든 상태를 내부적으로 관리합니다. 입력 타입은 `X2beeSimplePagination`이며, `paginationProps`, 기존의 `rows`, `pagination`, `pageSizeOptions`와 동시에 사용할 수 없습니다. `paginationProps` 내의 `pageSizeOptions`는 옵션으로 지정할 수 있으며, 제공되지 않을 경우 기본값은 `[100, 500, 1000, 5000, 10000]`입니다. |
| enableNoColumn  | 이 옵션은 기본 열 "No"의 가시성을 설정할 수 있게 해줍니다. 기본값은 true입니다.                                                                                                                                                                                                                                                                                                                                                                                                        |
| {% endstep %}   |                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

{% step %}

### 컴포넌트 사용 가이드

X2beeDataGrid 컴포넌트는 DataGridPremium 컴포넌트를 감싸, 내부적으로 MUI에서 제공하지 않는 추가, 수정, 삭제 상태를 관리하는 역할을 합니다.

* 그리드는 자체적으로 공간을 가지지 않으므로, 반드시 상위 태그 및 컴포넌트에서 공간을 확보해야 정상적으로 렌더링됩니다. 참고: <https://mui.com/x/react-data-grid/layout/>
* 필수로 입력해야 하는 옵션값은 `columns`와 `rows` 또는 `paginationProps`입니다. `paginationProps`와 `rows`는 반드시 둘 중 하나만 입력해야 합니다.

예시:

{% code title="사용 예시" %}

```jsx
<Box sx={{ height: '500px' }}>
  <X2beeDataGrid
    dataRef={dataRef}
    apiRef={apiRef}
    columns={columns}
    checkboxSelection
    onCellDoubleClick={onCellDoubleClickHandler}
    slotProps={{
      columnsManagement: { getTogglableColumns: () => columns.map((el) => el.field) },
      baseCheckbox: { disabled: baseCheckboxDisable }
    }}
    slots={{
      toolbar: () => CustomButtons({
        changeCheckBoxSelectable: (value) => setBaseCheckboxDisable(!value)
      })
    }}
    // pagination용 추가가 필요한 props
    paginationProps={{ fetch: async (paginationModel) => fetch(paginationModel.page, paginationModel.pageSize) }}
  />
</Box>
```

{% endcode %}
{% endstep %}
{% endstepper %}

***

## 그리드 API

{% stepper %}
{% step %}

### 1. 커스텀 그리드 API(dataRef)

* MUI에서 제공되는 그리드 API는 [MUI apiRef 정보](https://mui.com/x/api/data-grid/grid-api/)에서 자세하게 확인할 수 있습니다.
* X2beeDataGrid 컴포넌트 내부의 상태값등에 접근하며, 커스텀 기능들을 호출가능하게 해주는 API 객체입니다.

| **기능**                                                                         | **설명**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| useDataRef()                                                                   | 이 훅은 `dataRef`에 대한 객체를 제공합니다. 예: const dataRef = useDataRef(); \<X2beeDataGrid dataRef={dataRef} ... />                                                                                                                                                                                                                                                                                                                                                                                                                  |
| dataRef.current.all()                                                          | 오브젝트는 `update`, `create`, `delete`라는 세 가지 프로퍼티를 포함하여 반환됩니다. 각 프로퍼티는 수정된 행, 추가된 행, 삭제된 행을 배열 형태로 제공합니다.                                                                                                                                                                                                                                                                                                                                                                                                                   |
| dataRef.current.grid()                                                         | <p>다음은 반환되는 함수 목록 입니다.<br>selectClear, selectDelete, allClear, addRow, setUpdateRows export type X2beeDataGridDataRefGridFnType = { selectClear: () => void // 선택 행 변경취소 selectDelete: () => void // 선택 행 삭제 allClear: () => void // 전체 행 변경취소 addRow: () => void // 행 추가 save: () => void // fetchSave 호출 validation: ValidationFunction // update 및 create 행 valid asyncValidation: AsyncValidationFunction // update 및 create 행 valid를 비동기로 시행 setUpdateRows: (ids: GridRowId\[]) => void // updateRowId를 직접 추가 }</p> |
| dataRef.current.fetch()                                                        | 수동으로 grid에 fetch를 진행할때 사용됩니다. `paginationProps`을 사용하여 fetch를 구현한경우 반드시 해당 기능으로 fetch를 진행해야 합니다.                                                                                                                                                                                                                                                                                                                                                                                                                          |
| dataRef.current.create() / dataRef.current.update() / dataRef.current.delete() | `dataRef.current.all()`에서 제공되는 값들을 별도로 제공하는 함수입니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| dataRef.current.validation()                                                   | 그리드의 모든 editRow에대해 validation을 진행합니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| {% endstep %}                                                                  |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

{% step %}

### 2. API 객체 활용 가이드

MUI에서는 그리드를 제어할 수 있는 API 객체를 제공합니다. X2beeDataGrid에서도 그리드의 등록, 수정, 삭제 관련 기능 및 데이터를 API 객체를 통해 활용할 수 있습니다.

예시:

{% code title="사용 예시" %}

```jsx
const dataRef = useDataRef();
const apiRef = useGridApiRef();
return (
  <>
    <Box>
      <Markdown children={content} />
    </Box>
    <Stack direction="column">
      <Box sx={{ border: 1 }}>데이터 출력</Box>
      <Box sx={{ border: 1 }}>{JSON.stringify(showData)}</Box>
    </Stack>
    <Stack direction="row" borderBottom={1}>
      <Button onClick={() => setShowData(dataRef.current.create())}> 추가 데이터 가져오기 </Button>
      <Button onClick={() => setShowData(dataRef.current.update())}> 수정 데이터 가져오기 </Button>
      <Button onClick={() => setShowData(dataRef.current.delete())}> 삭제 데이터 가져오기 </Button>
      <Button onClick={() => setShowData(dataRef.current.all())}> 모든 데이터 가져오기 </Button>
    </Stack>
    <Box height="500px">
      <X2beeDataGrid
        columns={columns}
        rows={gridSampleRows}
        dataRef={dataRef}
        apiRef={apiRef}
        enableRowState
        // pagination을 위한 추가 props
        fetchSave={(allGridData, validation) => {
          const errorMessage = validation();
          if (errorMessage) alert(errorMessage);
        }}
      />
    </Box>
  </>
);
```

{% endcode %}
{% endstep %}
{% endstepper %}

***

## X2beeDataColDef

{% stepper %}
{% step %}

#### 1. 그리드 컬럼 제공 옵션

* MUI에서 제공하는 옵션은 [MUI 컬럼 옵션](https://mui.com/x/api/data-grid/grid-col-def/) 에서 확인 할 수 있습니다.
* X2beeGridColDef 에서 제공하는 추가옵션은 다음과 같습니다.

| **옵션명**         | **타입**                                                                                                                                                          | **설명**                                                                                                                                                                                                                                                                                      |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| visible         | boolean                                                                                                                                                         | 열이 초기에 노출될지 여부를 설정 가능합니다. initialState의 column hide를 단순화 한 옵션입니다.                                                                                                                                                                                                                           |
| defaultValue    | any                                                                                                                                                             | 열 추가시 기본으로 지정되는 값입니다.                                                                                                                                                                                                                                                                       |
| validBlank      | boolean                                                                                                                                                         | 셀 수정시, 빈값을 허용할지 여부를 설정 가능합니다.                                                                                                                                                                                                                                                               |
| validation      | X2beeGridValidation\[]                                                                                                                                          | 셀 수정시, 값 검증을 위해 사용됩니다. `valid`와 `message`를 prop으로 가지며, `valid`의 리턴값이 `false`일 경우, 셀에서 `message`가 오류로 출력됩니다. 제공되는 params는 다음과 같습니다. { id, value, field, row, otherProps, apiRef }                                                                                                            |
| dateEditor      | Object                                                                                                                                                          | 셀 수정시, DatePicker 사용할 옵션을 설정합니다. `type`을 통해 노출할 달력 형식을 지정할 수 있으며 결과 값을 `dateFormat`을 통해 지정할 수 있습니다. 제공되는 params는 다음과 같습니다. { type: 'date' \| 'dateTime', dateFormat: string, minDate: string, maxDate: string }                                                                             |
| dynamicEditable | [코드 참조](https://gitlab.x2bee.com/team-project/ec3-1/bo-mui/-/blob/main/src/lib/x2bee-data-grid/x2bee-data-grid-types.ts)`X2beeGridColDef`의 `dynamicEditable`참조. | 다른 셀의 값에 의해 해당 셀의 `editable`여부가 동적으로 변경되는 옵션입니다. 사용시 `editable`은 따로 선언해주지 않으셔도 됩니다. 제공되는 params는 다음과 같습니다. { otherProps: GridValidRowModel, apiRef: MutableRefObject, dataRef: MutableRefObject\<X2beeDataGridDataRefType> } 리턴으로 돌려줘야하는 값은 다음과 같습니다. { editable: boolean, value?: string } |
| {% endstep %}   |                                                                                                                                                                 |                                                                                                                                                                                                                                                                                             |

{% step %}

#### 2. 확장된 컬럼 타입 정의 및 유효성 검증 가이드

MUI 에서 제공하는 `GridColDef` 타입을 상속 받아 옵션을 추가한 컬럼 타입입니다. 기존의 MUI의 기능 및 X2beeDataGrid에서 추가적으로 제공하는 기능들을 정의 가능합니다.

예시:

{% code title="cols 예시" %}

```ts
const cols: X2beeGridColDef[] = [
  {
    headerName: '상품번호',
    field: 'goodsNo',
    editable: true,
    width: 150,
    validBlank: '상품번호를 입력해 주세요',
    validation: [
      {
        valid: (params) => {
          if (params.otherProps.goodsType === '묶음상품') {
            const typeInt = parseInt(params.value as string, 10) ?? 1000
            return typeInt >= 1000
          }
          return true
        },
        message: '묶음상품인경우 상품번호가 1000보다 커야합니다'
      },
      {
        valid: (params) => {
          if (params.otherProps.goodsType === '일반상품') {
            const typeInt = parseInt(params.value as string, 10) ?? 1000
            return typeInt < 1000
          }
          return true
        },
        message: '일반상품인경우 상품번호가 1000보다 작아야합니다'
      }
    ]
  },
  {
    headerName: '상품명',
    field: 'goodsNm',
    type: 'string',
    editable: true,
    width: 150,
    validBlank: true,
    validation: [
      {
        valid: (params) => {
          const allRow = params.apiRef.current.getRowModels()
          const allRowIds = params.apiRef.current.getAllRowIds()
          return allRowIds.every(
            (id) => params.id === id || allRow.get(id)?.[params.field] !== params.value
          )
        },
        message: '중복된 상품명은 사용할수 없습니다.'
      }
    ]
  },
  {
    headerName: '상품타입',
    field: 'goodsType',
    type: 'singleSelect',
    valueOptions: [...GoodsTypeArr],
    defaultValue: GoodsTypeArr[0],
    editable: true,
    width: 150
  },
  {
    headerName: '가격',
    field: 'price',
    type: 'number',
    editable: true,
    width: 150,
    validation: [
      {
        valid: (params) => (params.value as number) > 0,
        message: '가격은 0보다 커야합니다'
      }
    ]
  },
  {
    headerName: '등록일',
    field: 'regDate',
    editable: true,
    width: 150,
    defaultValue: dayjs().format(),
    dateEditor: {
      type: 'date',
      dateFormat: 'YYYYMMDD',
      minDate: dayjs().format(),
      maxDate: '2050-12-31'
    }
  }
]
```

{% endcode %}
{% endstep %}
{% endstepper %}

***

## Common-toolbar

{% stepper %}
{% step %}

#### 1. 툴바 및 버튼 컴포넌트 구조

| **컴포넌트**          | **타입**   | **설명**                                |
| ----------------- | -------- | ------------------------------------- |
| Toolbar.Container |          |                                       |
| Toolbar.Button    | add      | 행 추가 기능입니다.                           |
|                   | remove   | 행 삭제 기능입니다.                           |
|                   | reset    | 선택한 행 초기화 기능입니다.                      |
|                   | resetAll | 모든 행에대한 초기화 기능입니다.                    |
|                   | save     | 데이터 그리드에 fetchSave로 입력한 함수를 실행합니다.    |
|                   | excel    | 엑셀 다운로드 기능을 제공합니다.                    |
|                   | custom   | 커스텀 버튼을 제공합니다. 버튼 자체로는 기능을 제공하지 않습니다. |
| {% endstep %}     |          |                                       |

{% step %}

#### 2. 툴바 확장 및 커스터마이징 가이드

이 가이드는 MUI에서 제공하는 `GridToolbarContainer`를 감싸 기능을 추가한 컴포넌트를 상세히 설명합니다.

그리드에 필요한 기본 기능인 열 추가, 삭제, 초기화 외에도 커스텀 버튼 및 엑셀 다운로드 버튼을 추가할 수 있는 방법을 안내합니다.

예시:

{% code title="툴바 슬롯 예시" %}

```jsx
<X2BeeDataGrid
  ...
  slots={{
    toolbar: () => (
      <Toolbar.Container
        leftSlot={
          <>
            <Toolbar.Button type="add" title="기획전 등록" onClick={() => console.log('등록 팝업')} />
            <Toolbar.Button type="remove" />
            <Toolbar.Button type="resetAll" />
            <Toolbar.Button type="save" />
            <Toolbar.Button type="custom" title="일괄 변경" onClick={() => console.log('일괄 변경')} />
          </>
        }
        rightSlot={
          <>
            <Toolbar.Button type="excel" />
            <Toolbar.Button type="custom" title="복사" onClick={() => console.log('복사')} />
          </>
        }
      />
    )
  }}
/>
```

{% endcode %}
{% endstep %}
{% endstepper %}


# 파일 업로드 및 대용량 엑셀 다운로드

프로젝트에서 파일 업로드 및 대용량 엑셀 다운로드를 구현하기 위한 가이드로 각 기능의 사용 방법과 코드 예제 및 설정 방법을 제공합니다.

* 파일 업로드는 REST API와 FormData를 활용하며 다양한 저장소(WAS/NAS, FTP, AWS S3, Azure Blob Storage) 옵션을 제공합니다.
* 대용량 엑셀 다운로드는 최신 MyBatis Cursor 기능을 활용한 대용량 데이터 처리 효율성을 극대화 합니다.

***

## 파일 업로드

{% stepper %}
{% step %}

### 기본 사용 흐름

Plugin으로 제공하는 restApi.ts에서 `uploadPost`를 이용해 파일 업로드를 처리하고, 업로드할 파일을 FormData에 추가하여 서버로 전송합니다.

필드 컴포넌트(fields.tsx)에서 `Upload`, `UploadBox`를 사용해 파일의 확장자와 용량 제한을 검증하여 안정성을 확보할 수 있습니다.

저장소 옵션은 WAS/NAS, FTP, AWS S3, Azure Blob Storage 중 선택하여 저장소를 구성합니다.

`@Configuration` `@Bean`으로 저장소 옵션에 설정한 정보로 upload 메소드를 정의하여 `@Qualifier("uploader")` 으로 사용합니다.
{% endstep %}

{% step %}

### 파일 업로드 (클라이언트) 예시

```javascript
const fetchUploadFile = async (files: File[]) => {
  const formData = new FormData();
  files.forEach((file) => {
    if (file) formData.append('files', file);
  });
  const response = (await restApi.uploadPost(
    `/api/bo/...`,
    { form: formData }
  )) as ResponseEntity;
  return response;
};
```

{% endstep %}

{% step %}

### Component를 사용한 유효성 검증 예시

```tsx
export default function Upload() {
  const { setValue } = useFormContext();

  const validator = <T extends File>(file: T) => {
    const fileType = file.type;
    const fileSize = file.size;
    const validTypes = Object.keys(UPLOAD_IMAGE_ACCEPT);

    if (!validTypes.includes(fileType)) {
      return { message: '확장자가 올바르지 않습니다.', code: 'ERR_TYPE' };
    }
    if (GOODS_UPLOAD.MAX_IMAGE_SIZE < fileSize) {
      return { message: '최대 용량을 초과하였습니다.', code: 'ERR_SIZE' };
    }
    return null;
  };

  const onUpload = (files: File[]) => {
    setValue('File', files);
  };

  const onDelete = () => {
    setValue('File', null);
  };

  return (
    <Field.Upload
      name="fileUpload"
      accept={UPLOAD_IMAGE_ACCEPT}
      validator={validator}
      onDelete={onDelete}
      onDrop={onUpload}
    />
  );
}
```

{% endstep %}

{% step %}

### 업로드 관련 상수 예시

```ts
export const UPLOAD_IMAGE_ACCEPT = {
  'image/png': [],
  'image/jpg': [],
  'image/jpeg': [],
  'image/gif': []
};

export const UPLOAD_VIDEO_ACCEPT = {
  'video/mp4': [],
  'video/avi': [],
  'video/mov': []
};

// 전시
export const DISPLAY_UPLOAD = {
  MAX_IMAGE_SIZE: 10485760,
  MAX_VIDEO_SIZE: 104857600
};

// 상품
export const GOODS_UPLOAD = {
  MAX_IMAGE_SIZE: 10485760,
  MAX_VIDEO_SIZE: 104857600
};
```

(원본: upload-constants.ts)
{% endstep %}

{% step %}

### API 저장소 설정 (Spring Boot applcation.yml 예시)

```yaml
upload:
  type: s3 # file : WAS / NAS, ftp : FTP, s3 : AWS S3, azure : Azure Blob Storage
  root-path: files/ # 파일업로드 시 저장될 경로를 설정합니다.
  file:
    base-path: /data/ # was의 기본 업로드 경로 또는 mount 되는 nas의 기본 경로를 설정합니다.
    base-path-video: /data/video
  ftp:
    host: 192.168.2.247
    user: test
    password: test
  azure:
    connection: DefaultEndpointsProtocol=https;AccountName=testblob;AccountKey=....
    container: test

# aws의 경우에는 스프링에서 정한 설정 변수를 사용함.
cloud:
  aws:
    s3:
      bucket: x2bee-stg-pri-attachment-s3
    s3-video-origin:
      bucket: x2bee-stg-pri-attachment-s3
region:
  static: ap-northeast-2
stack:
  auto: false
```

{% endstep %}

{% step %}

### Spring Uploader Bean 설정 예시 (UploaderConfig.java)

```java
package com.x2bee.api.common.base.config;

import com.x2bee.common.base.upload.UploaderCommonImpl;
import com.x2bee.common.base.upload.Uploader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration
public class UploaderConfig {

    @Autowired
    private Environment env;

    @Value("${upload.file.base-path:#{null}}")
    private String basePath;
    @Value("${upload.file.base-path-video:#{null}}")
    private String videoBasePath;

    @Value("${upload.ftp.host:}")
    private String ftpHost;
    @Value("${upload.ftp.user:}")
    private String ftpUser;
    @Value("${upload.ftp.password:}")
    private String ftpPassword;

    @Value("${cloud.aws.region.static:#{null}}")
    private String awsRegion;
    @Value("${cloud.aws.s3.bucket:#{null}}")
    private String bucket;
    @Value("${cloud.aws.s3-video-origin.bucket:#{null}}")
    private String videoBucket;

    @Value("${upload.azure.connection:#{null}}")
    private String azureConnection;
    @Value("${upload.azure.container:#{null}}")
    private String azureContainer;

    @Bean(name = "uploader")
    // apllication.yml 설정값으로 bean 모듈을 생성하여 파일업로드를 합니다.
    public Uploader uploader() {
        return new UploaderCommonImpl.Builder(env)
            .basePath(basePath)
            .ftpHost(ftpHost)
            .ftpUser(ftpUser)
            .ftpPassword(ftpPassword)
            .awsRegion(awsRegion)
            .bucket(bucket)
            .azureConnection(azureConnection)
            .azureContainer(azureContainer)
            .build();
    }

    @Bean(name = "videoUploader")
    public Uploader videoUploader() {
        return new UploaderCommonImpl.Builder(env)
            .basePath(videoBasePath)
            .ftpHost(ftpHost)
            .ftpUser(ftpUser)
            .ftpPassword(ftpPassword)
            .awsRegion(awsRegion)
            .bucket(videoBucket)
            .build();
    }
}
```

(원본: UploaderConfig.java)
{% endstep %}

{% step %}

### SampleServiceImpl.java 예시 (서버 업로드 처리)

```java
@Service
@Slf4j
@RequiredArgsConstructor
public class SampleServiceImpl implements SampleService {

    @Qualifier("uploader")
    private final Uploader uploader;

    @Qualifier("videoUploader")
    private final Uploader videoUploader;

    @Qualifier("fileuUploader")
    private final Uploader fileuUploader;

    @Override
    public String fileUpload(HttpServletRequest httpServletRequest) throws Exception {
        AtomicReference<String> result = new AtomicReference<>("");
        MultipartHelper.handle(httpServletRequest, (fieldName, fileName, fileSize, inputStream, multipartFile) -> {
            UploadReqDto uploadReqDto = new UploadReqDto();
            uploadReqDto.setAttacheFileKind(AttacheFileKind.SYSTEM);
            uploadReqDto.setTempPathYn(false);
            uploadReqDto.setCustomPath("");
            uploadReqDto.setTypeCd("10");

            // 기존 AWS에 업로드
            Map<String, Object> retMap = uploader.upload(multipartFile, uploadReqDto); // UploaderConfig Bean 모듈 호출
            log.debug("fileUpload : {}", retMap);
            UploadResDto uploadResDto = (UploadResDto)((Map<String, Object>)retMap.get("data")).get("data");
            result.set(uploadResDto.getUrl());
            log.debug("url : {}", uploadResDto.getUrl());

            // 기존 AWS 비디오 공간에 업로드
            videoUploader.upload(multipartFile, uploadReqDto);

            // 파일 시스템에 업로드
            fileUploader.upload(multipartFile, uploadReqDto);
        });
        return result.get();
    }
}
```

{% endstep %}
{% endstepper %}

***

## 대용량 엑셀 다운로드

대용량 엑셀 다운로드 구현 시 메모리 효율성을 위해 MyBatis(3.2.4 이상)의 Cursor를 활용하고, `@ExcelDownLoad` 어노테이션과 `ExcelUtil.createCursorExcel` 메서드를 사용합니다. Postman을 활용해 테스트를 지원하며, TypeScript 유틸도 제공합니다.

{% stepper %}
{% step %}

### MyBatis Mapper 예시 (Cursor 사용)

```java
/* SampleMapper.java */
public interface SampleMapper {
    Cursor<SampleZipNoMgmtResponse> getZipNoList(SampleZipNoMgmtRequest sampleZipNoMgmtRequest);
}
```

{% endstep %}

{% step %}

### MyBatis XML 쿼리 예시 (Sample.xml)

```xml
<!--Dto, Xml 예제-->
<select id="getZipNoList" parameterType="SampleZipNoMgmtRequest" resultType="SampleZipNoMgmtResponse" >
WITH TMP1 AS (
  SELECT ZIP_NO_SEQ, ZIP_NO, CTP_NM, SIG_NM, HEMD_NM, LNBR_MNNM, LNBR_SLNO, ROAD_NM
  FROM ST_ZIP_NO
  WHERE USE_YN = 'Y'
  <if test="ctpNmParam != null and ctpNmParam != ''">
    AND CTP_NM = #{ctpNmParam}
  </if>
  <if test="sigNmParam != null and sigNmParam != ''">
    AND SIG_NM LIKE '%' || #{sigNmParam}
  </if>
)
SELECT ZIP_NO_SEQ, ZIP_NO, CTP_NM, SIG_NM, HEMD_NM, LNBR_MNNM, LNBR_SLNO, ROAD_NM
FROM TMP1
ORDER BY 1
LIMIT 1000000
</select>
```

{% endstep %}

{% step %}

### Controller 샘플

```java
/* 대용량 엑셀다운로드 샘플 */
@GetMapping("/exceldown")
public void exceldown(SampleZipNoMgmtRequest sampleZipNoMgmtRequest) throws Exception {
    sampleService.exceldown(sampleZipNoMgmtRequest);
}
```

{% endstep %}

{% step %}

### Service 구현 예시 (ExcelUtil.createCursorExcel)

```java
/* SampleServiceImpl.java */
@ExcelDownLoad
public void exceldown(SampleZipNoMgmtRequest sampleZipNoMgmtRequest) {

    // sample 1
    ExcelUtil.createCursorExcel(() -> sampleMapper.getZipNoList(sampleZipNoMgmtRequest));

    // sample 2 (추가 옵션)
    ExcelUtil.createCursorExcel(
        () -> sampleMapper.getZipNoList(zipNoMgmtRequest),
        ExcelEntity.builder()
            .fileName("TRGMN_LIST")
            .sheetName("SHEET1")
            .build()
    );
}
```

{% endstep %}
{% endstepper %}

참고: Postman 샘플파일 [\[Sample\].postman\_collection.json](https://tech.x2bee.com/download/attachments/99352577/%5BSample%5D.postman_collection.json)

Postman에서 Excel 다운로드 테스트 시 Send and Download 옵션을 사용합니다.

대용량 엑셀 다운로드 시, 제공되는 download-utils.ts의 `downloadStaticFile`을 사용합니다:

```ts
downloadStaticFile({
  method: 'get',
  fileUrl: '/api/bo/v1/sample/exceldown',
  downloadedFileName: '파일명'
});
```

***

## Attachments

Document generated by Confluence on 2025-12-18 03:31 오전

Atlassian: <http://www.atlassian.com/>


# 데이터베이스 활용 및 포맷

## 개요

데이터베이스를 활용하고 포멧하는 방법에 대해 설명합니다. 개발 환경 데이터베이스 설정, 트랜잭션 처리, 유효성 체크 방법, 날짜 변환 유틸 사용법, 대용량 엑셀 다운로드, FO/BO 숫자/날짜(dayjs) 변환 가이드 등에 대해 자세히 안내합니다.

***

## 문서 구성

<details>

<summary><strong>개발 환경 데이터 베이스</strong> </summary>

개발 환경에서 사용되는 데이터베이스 설정과 구성에 대해 설명하고 데이터베이스의 초기 설정 및 관리 방법을 이해할 수 있습니다.

</details>

<details>

<summary><strong>트랜잭션 처리</strong> </summary>

데이터베이스의 일관성을 유지하기 위해 트랜잭션의 구현 방법 및 관리에 대해서 설명합니다.

</details>

<details>

<summary><strong>유효성 체크 방법</strong></summary>

입력 데이터의 유효성을 체크하고 관리하는 방법에 대해 안내하고, 잘못된 데이터가 데이터베이스에 저장되는 것을 방지하는 방법을 설명합니다.

</details>

<details>

<summary><strong>날짜 변환 유틸 사용법</strong> </summary>

날짜 데이터를 다룰 때 필요한 변환 및 처리 방법을 설명합니다.

</details>

<details>

<summary><strong>대용량 엑셀 다운로드</strong> </summary>

대용량 데이터를 효과적으로 처리하고 엑셀 파일로 다운로드하는 방법을 설명합니다.

</details>

<details>

<summary><strong>FO/BO 숫자/날짜(dayjs) 변환 가이드</strong> </summary>

프론트 및 백 오피스 간의 데이터 형식을 효과적으로 변환 할 수 있도록 처리하는 방법을 설명합니다.

각 문서의 상세 내용은 X2BEE의 기술적인 세부 정보를 포함하고 있으며, 프로젝트 개발을 지원하기 위한 자세한 내용을 다루고 있습니다.

</details>

&#x20;<br>

&#x20;<br>

<br>

<br>

<br>


# 개발환경 데이터베이스

X2BEE 개발 환경에서 데이터 베이스 연결과 다중 데이터베이스 연결에 대해 설명합니다.

{% stepper %}
{% step %}

### application.yml 파일 내 datasource 관련 설정 추가

아래와 같이 application.yml 파일에서 서버 port번호와 데이터베이스 관련 설정 내용을 작성합니다.

예시: RO/RW 별로 데이터 소스 작성

application.yml ... spring: datasource: {dbname}rodb {dbname}rwdb ...

{databasename}rodb / {databasename}rwdb 추가 작성하여 다중연결 가능합니다.
{% endstep %}

{% step %}

### {dbname}ro / {dbname}rw 데이터베이스 연결

아래는 application.yml 예시(일부 생략된 형태)입니다.

{% code title="application.yml (예시)" %}

```yaml
spring:
  config:
    activate:
      on-profile: local
  mvc:
    log-request-details: true
  zipkin:
    enabled: false
  devtools:
    livereload:
      port: 3${server.port}
    restart:
      exclude: mapper/**
  datasource:
    displayrodb:
      url: jdbc:log4jdbc:postgresql://ec2-43-201-119-5.ap-northeast-2.compute.amazonaws.com:5432/x2bee_main?currentSchema=x2bee_main
      driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      username: 
      password:  #ENC(yGUobhgkk3gx5KFEq2XEOv8TMMY6Nm8N58S8VJG/qnnYZEMdMy3GClbWHxJKJSxu)
      hikari:
        maximum-pool-size: 5
        minimum-idle: 3
        connection-timeout: 30000
        validation-timeout: 5000
        max-lifetime: 1800000
        idle-timeout: 300000
        pool-name: HikariPool-apibo-displayrodb

    displayrwdb:
      url: jdbc:log4jdbc:postgresql://ec2-43-200-110-112.ap-northeast-2.compute.amazonaws.com:5432/x2bee_main?currentSchema=x2bee_main
      driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      username: x2bee_main
      password: x2beemain@11 #ENC(yGUobhgkk3gx5KFEq2XEOv8TMMY6Nm8N58S8VJG/qnnYZEMdMy3GClbWHxJKJSxu)
      hikari:
        maximum-pool-size: 5
        minimum-idle: 3
        connection-timeout: 30000
        validation-timeout: 5000
        max-lifetime: 1800000
        idle-timeout: 300000
        pool-name: HikariPool-apibo-displayrwdb
```

{% endcode %}
{% endstep %}

{% step %}

### 데이터베이스 관련 설정 정보

프로퍼티와 설명:

* port: tomcat 포트
* url: 데이터베이스 접속 URL
* username: 데이터베이스 사용자 아이디
* password: 데이터베이스 사용자 비밀번호
* driveClassName: 데이터베이스 드라이버 클래스 명
* hikari: 기타 HikariCP 관련 설정
* session: 스프링 session 설정
* zipkin: MSA 환경에서 분산 트렌젝션의 추적
  {% endstep %}

{% step %}

### DatabaseConfig.java 파일 작성 예시

아래는 DisplayRodbDatabaseConfig.java의 예시입니다.

{% code title="DisplayRodbDatabaseConfig.java" %}

```java
package com.x2bee.api.bo.base.config;

import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@MapperScan(value="com.x2bee.api.bo.app.repository.displayrodb", sqlSessionFactoryRef="displayRodbSqlSessionFactory")
public class DisplayRodbDatabaseConfig {

    @Bean(name = "displayRodbDataSource")
    @ConfigurationProperties(prefix = "spring.displayrodb.datasource")
    public DataSource displayRodbDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "displayRodbSqlSessionFactory")
    public SqlSessionFactory displayRodbSqlSessionFactory(@Qualifier("displayRodbDataSource") DataSource displayRodbDataSource,
                                                         ApplicationContext applicationContext) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(displayRodbDataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.x2bee.api.bo.app");
        sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:mapper/displayrodb/**/*.xml"));
        sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource("classpath:mapper/mybatis-config.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean(name = "displayRodbSqlSessionTemplate")
    public SqlSessionTemplate displayRodbSqlSessionTemplate(SqlSessionFactory displayRodbSqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(displayRodbSqlSessionFactory);
    }

    // ...
}
```

{% endcode %}
{% endstep %}

{% step %}

### mybatis-config.xml 파일 작성

데이터 마스킹, 암호화 관련 Java 파일을 MyBatis 플러그인으로 연결하는 예시입니다.

{% code title="mybatis-config.xml" %}

```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>
    <setting name="mapUnderscoreToCamelCase" value="true" />
  </settings>
  <plugins>
    <plugin interceptor="com.x2bee.api.bo.base.masking.MybatisMaskingInterceptor"/>
    <plugin interceptor="com.x2bee.common.base.encrypt.MybatisEncryptInterceptor"/>
  </plugins>
</configuration>
```

{% endcode %}
{% endstep %}

{% step %}

### 데이터베이스 데이터 마스킹, 암호화 관련 Java 파일 작성

아래는 MyBatis 결과/업데이트 시 데이터 마스킹 및 암복호화를 처리하는 인터셉터 예시들입니다.

MybatisMaskingInterceptor.java

{% code title="MybatisMaskingInterceptor.java" %}

```java
package com.x2bee.api.bo.base.masking;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import com.x2bee.common.base.context.ApplicationContextWrapper;
import com.x2bee.common.base.masking.MaskString;

/**
 * Result interceptor
 */
@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }))
public class MybatisMaskingInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        BoApiMaskingUtils maskingUtils = (BoApiMaskingUtils)ApplicationContextWrapper.getBean("boApiMaskingUtils");
        Object result = invocation.proceed();
        if (Objects.isNull(result)){
            return null;
        }
        if (result instanceof ArrayList) {
            ArrayList<?> resultList = (ArrayList<?>) result;
            for (int i = 0; i < resultList.size(); i++) {
                Field[] fields = resultList.get(i).getClass().getDeclaredFields();
                for (Field field : fields) {
                    MaskString annotation = field.getAnnotation(MaskString.class);
                    if(annotation!=null && field.getType() == String.class) {
                        field.setAccessible(true);
                        String val = maskingUtils.getValue(field.get(resultList.get(i))+"", annotation.type());
                        try {
                            field.set(resultList.get(i), val);
                        } catch (IllegalAccessException e) {
                            System.out.println(e.getMessage());
                        }
                    }
                }
            }
        } else {
            Field[] fields = result.getClass().getDeclaredFields();
            for (Field field : fields) {
                MaskString annotation = field.getAnnotation(MaskString.class);
                if(annotation!=null && field.getType() == String.class) {
                    field.setAccessible(true);
                    String val = maskingUtils.getValue(field.get(result)+"", annotation.type());
                    try {
                        field.set(result, val);
                    } catch (IllegalAccessException e) {
                        System.out.println(e.getMessage());
                    }
                }
            }
        }
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
```

{% endcode %}

MybatisEncryptInterceptor.java

{% code title="MybatisEncryptInterceptor.java" %}

```java
package com.x2bee.common.base.encrypt;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import lombok.extern.slf4j.Slf4j;

/**
 * @author choiyh44
 * @version 1.0
 * @since 2021. 12. 6.
 */
@Slf4j
@Intercepts({
    @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class })
})
public class MybatisEncryptInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        String method = invocation.getMethod().getName();
        if ("update".equals(method)) {
            return processUpdate(invocation);
        } else if ("handleResultSets".equals(method)) {
            return processQuery(invocation);
        } else {
            return invocation.proceed();
        }
    }

    private Object processUpdate(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        Object[] args = invocation.getArgs();
        Object param = args[1];
        if (param != null) {
            Field[] fields = param.getClass().getDeclaredFields();
            for (Field field : fields) {
                Encrypt annotation = field.getAnnotation(Encrypt.class);
                if(annotation!=null && field.getType() == String.class) {
                    field.setAccessible(true);
                    try {
                        String val = EncryptUtils.getEncryptValue(field.get(param)+"", annotation.type());
                        log.info("EncryptValue: {}: {}", val.length(), val);
                        field.set(param, val);
                    } catch (Exception e) {
                        log.warn(e.getMessage(), e);
                    }
                }
            }
        }
        return invocation.proceed();
    }

    private Object processQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        Object result = invocation.proceed();
        if (Objects.isNull(result)){
            return null;
        }
        if (result instanceof ArrayList) {
            ArrayList<?> resultList = (ArrayList<?>) result;
            for (int i = 0; i < resultList.size(); i++) {
                Field[] fields = resultList.get(i).getClass().getDeclaredFields();
                for (Field field : fields) {
                    Encrypt annotation = field.getAnnotation(Encrypt.class);
                    if(annotation!=null && field.getType() == String.class) {
                        field.setAccessible(true);
                        try {
                            String val = EncryptUtils.getDecryptValue(field.get(resultList.get(i))+"", annotation.type());
                            field.set(resultList.get(i), val);
                        } catch (Exception e) {
                            System.out.println(e.getMessage());
                        }
                    }
                }
            }
        } else {
            Field[] fields = result.getClass().getDeclaredFields();
            for (Field field : fields) {
                Encrypt annotation = field.getAnnotation(Encrypt.class);
                if(annotation!=null && field.getType() == String.class) {
                    field.setAccessible(true);
                    try {
                        String val = EncryptUtils.getDecryptValue(field.get(result)+"", annotation.type());
                        field.set(result, val);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                }
            }
        }
        return result;
    }
}
```

{% endcode %}
{% endstep %}
{% endstepper %}

##


# 트랜잭션 처리

트랜잭션 처리는 다음과 같은 방식을 따릅니다.

1. **트랜잭션 관리 방식**

* 트랜젝션은 `@Transactional` 어노테이션을 통해 명시적으로 관리함(2023년 5월 개정)

2. **데이터베이스 구조 및 연결 원칙**

X2BEE Framework는 ReadWrite 데이터베이스(Master)와 ReadOnly 데이터베이스(Slave)로 구성된 이중화 구조를 기본으로 가정함.

개발 편의성을 위한 연결 방식 제공:

* `@Transactional` 어노테이션이 선언된 클래스 및 메서드는 ReadWrite 데이터베이스(Master)에 연결
* `@Transactional` 어노테이션이 없는 Service 계층 메서드는 기본적으로 ReadOnly 데이터베이스(Slave)에 연결

3. **다중 데이터소스 및 트랜잭션 연결 (2023년 6월 추가)**
   * 서로 다른 데이터베이스 스키마 및 `TransactionManager`를 활용하여 다중 데이터소스를 트랜잭션으로 연결하는 방법 지원

***

## 트랜잭션 전파 레벨

* **Propagation.REQUIRED (기본 값)**\
  특정 메서드의 트랜잭션이 `Propagation.REQUIRED`로 설정되었을 때의 동작은 다음과 같다. 기본적으로 해당 메서드를 호출한 곳에서 별도의 트랜잭션이 설정되어 있지 않았다면 트랜잭션을 새로 시작한다(새로운 연결을 생성하고 실행). 만약 호출한 곳에서 이미 트랜잭션이 설정되어 있다면 기존의 트랜잭션 내에서 로직을 실행한다(동일한 연결 안에서 실행). 예외가 발생하면 롤백이 되고 호출한 곳에도 롤백이 전파된다. **Propagation.REQUIRED**는 기본값이므로 생략 가능. 다만 해당 메서드가 호출한 곳과 별도의 쓰레드라면 전파 레벨과 상관 없이 별도의 트랜잭션을 생성하여 실행한다. Spring은 내부적으로 트랜잭션 정보를 ThreadLocal 변수에 저장하기 때문에 다른 쓰레드로 트랜잭션이 전파되지 않음.
* **Propagation.REQUIRES\_NEW**\
  매번 새로운 트랜잭션을 시작한다(새로운 연결을 생성하고 실행). 호출한 곳에서 이미 트랜잭션이 설정되어 있다면 기존 트랜잭션은 메서드가 종료할 때까지 대기 상태가 되며, 새로운 트랜잭션은 독립적으로 실행된다. 새로운 트랜잭션에서 예외가 발생해도 호출한 곳에는 롤백이 전파되지 않는다.
* **Propagation.SUPPORTS**\
  이미 시작된 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 진행한다.
* **Propagation.NESTED**\
  기본적으로 REQUIRED와 동일하게 작동하나, SAVEPOINT를 지정한 시점까지 부분 롤백이 가능하다. 데이터베이스가 SAVEPOINT 기능을 지원해야 사용 가능(예: Oracle).
* **Propagation.MANDATORY**\
  이미 시작된 트랜잭션이 있으면 참여하지만, 없으면 예외를 발생시킨다. 독립적으로 트랜잭션을 진행하면 안 되는 경우 사용.
* **Propagation.NOT\_SUPPORTED**\
  트랜잭션을 사용하지 않음. 이미 진행 중인 트랜잭션이 있으면 보류시킨다.
* **Propagation.NEVER**\
  트랜잭션을 사용하지 않도록 강제. 이미 진행 중인 트랜잭션이 있으면 예외를 발생시킴.

자주 사용되는 전파 설정: REQUIRED, REQUIRES\_NEW, NESTED, SUPPORTS. 기본적으로는 REQUIRED와 트랜잭션을 분리하기 위한 REQUIRES\_NEW를 주로 사용하면 됨.

## 트랜잭션 경계 설정

ReadWrite 트랜잭션은 아래 AOP 코드에 의해 결정됩니다. `@Transactional` 어노테이션이 선언된 함수의 경우 ReadWrite로 동작합니다.

```java
/** ReadWrite Transaction 설정 AOP */
@Aspect
@Component
public class ReadWriteDatabaseTransactionAspect {
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object logging(ProceedingJoinPoint pjp) throws Throwable {
        RoutingDatabaseContextHolder.set(RoutingDatabase.READWRITE);
        Object result;
        try {
            result = pjp.proceed();
        } finally {
            RoutingDatabaseContextHolder.clear();
        }
        return result;
    }
}
```

ReadOnly 트랜잭션은 아래 AOP 코드에 의해 결정됩니다. 아래 코드에서는 `@Transactional` 어노테이션이 없는 서비스 클래스에 한해서만 ReadOnly 트랜잭션에 참여시키기 위해 한정자 `within(@org.springframework.stereotype.Service *)` 를 선언했습니다.

기본적으로 `@Transactional` 어노테이션이 없는 함수의 경우 ReadOnly로 동작하지만, 시작 함수가 `@Transactional`이 붙은 함수로 시작한 경우 `@Transactional` 어노테이션이 없는 조회 서비스라도 ReadWrite로 동작하게 됩니다.

```java
/** READONLY Transaction 설정 AOP */
@Aspect
@Component
public class ReadOnlyDatabaseTransactionAspect {
    @Around("within(@org.springframework.stereotype.Service *) && !@annotation(org.springframework.transaction.annotation.Transactional) && !@within(org.springframework.transaction.annotation.Transactional)")
    public Object logging(ProceedingJoinPoint pjp) throws Throwable {
        RoutingDatabase context = RoutingDatabaseContextHolder.getClientDatabase();
        if (context == RoutingDatabase.READWRITE) {
            RoutingDatabaseContextHolder.set(RoutingDatabase.READWRITE);
        } else {
            RoutingDatabaseContextHolder.set(RoutingDatabase.READONLY);
        }
        Object result;
        try {
            result = pjp.proceed();
        } finally {
            RoutingDatabaseContextHolder.clear();
        }
        return result;
    }
}
```

## Exception Rollback 처리

모든 Exception에 대해서 rollback 처리를 하기 위해 어노테이션 구조는 기본적으로 아래와 같습니다.

```java
@Transactional(rollbackFor = {Exception.class})
```

위 선언에서 propagation = Propagation.REQUIRES(=REQUIRED)가 기본값입니다.

트랜잭션의 rollback이 트리거되기 위해서는 기본적으로 RuntimeException과 그 하위 클래스여야 합니다. Checked Exception(예: Exception)을 캐치하여 rollback시키려면 `rollbackFor = {Exception.class}`처럼 명시적으로 설정해야 합니다.

### 작성 예 1

```java
public class SampleServiceImpl implements SampleService {
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public List<TestLog> txTest4() throws Exception {
        // 데이터 저장 READWRITE
        sampleService3.test2();
        // 데이터 수정 READWRITE
        sampleService3.testupdate1();
        throw new Exception("강제 오류 발생");
    }
}
...
public class SampleServiceImpl3 implements SampleService3 {
    private final SampleTrxMapper sampleTrxMapper;

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void test2() {
        TestLog test = new TestLog();
        test.setSeq(1);
        test.setLog(getValue());
        test.setTestValue(getValue());
        sampleTrxMapper.insertTestLog(test);
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void testupdate1() {
        TestLog test = new TestLog();
        test.setSeq(1);
        test.setLog(getValue());
        test.setTestValue(getValue());
        sampleTrxMapper.updateTestLog(test);
    }
}
```

위 예에서는 Propagation.REQUIRED가 작용하여 하나의 트랜잭션(연결) 위에서 작동하므로 Exception이 발생하면 전체가 rollback됩니다.

### 작성 예 2

```java
public class SampleServiceImpl implements SampleService {
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public List<TestLog> txTest10() {
        // 데이터 저장 READWRITE
        sampleService3.test2();
        try {
            // testinsert1에서 강제로 Exception 발생함.
            // 같은 트랜잭션이기 때문에 try-catch로 감싸도 test2()가 롤백됨.
            sampleService3.testinsert1();
        } catch (Exception ex) {
        }
        // READWRITE 조회
        List<TestLog> list = sampleService2.findTestLog();
        log.debug("txTest10 size : {}", list.size());
        return list;
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public List<TestLog> txTest11() {
        // 데이터 저장 READWRITE
        sampleService3.test2();
        try {
            // testinsert2에서 강제로 Exception 발생함.
            // REQUIRES_NEW로 새로운 트랜잭션이기 때문에 test2()가 롤백되지 않음.
            sampleService3.testinsert2();
        } catch (Exception ex) {
        }
        // READWRITE 조회
        List<TestLog> list = sampleService2.findTestLog();
        log.debug("txTest11 size : {}", list.size());
        return list;
    }
}
```

* txTest10: `sampleService3.testinsert1()`에서 발생한 Exception은 같은 트랜잭션이므로 전체가 rollback됨.
* txTest11: `sampleService3.testinsert2()`는 `REQUIRES_NEW`로 새로운 트랜잭션이므로 해당 부분만 rollback되고 `test2()`의 데이터는 유지됨.

### 작성 예 3

```java
public class SampleServiceImpl implements SampleService {
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public List<TestLog> txTest12() {
        for (int i = 0; i < 10; i++) {
            try {
                // 데이터 저장
                sampleService3.testinsertData(i);
                // 성공 로그 저장 (REQUIRES_NEW)
                sampleService3.testinsertSuccess();
            } catch (Exception ex) {
                // 실패 로그 저장 (REQUIRES_NEW)
                sampleService3.testinsertFailure();
            }
        }
        // READWRITE 조회
        List<TestLog> list = sampleService2.findTestLog();
        log.debug("txTest12 size : {}", list.size());
        return list;
    }
}
```

위 예에서 txTest12는 루프 도중 마지막에서 Exception이 발생하면 루프 내에서 수행한 대부분의 데이터 저장은 rollback되지만, 성공 로그와 실패 로그는 REQUIRES\_NEW 트랜잭션이므로 별도로 커밋되어 저장됩니다.

<figure><img src="/files/RLOxp1os0AddSJZMiaqK" alt=""><figcaption></figcaption></figure>

## 트랜잭션 분리

Service 함수 내에서 DAO(Repository) 호출 단위로 트랜잭션을 분리하기 위한 어노테이션 구조는 아래와 같습니다.

```java
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = {Exception.class})
```

작성 예:

```java
public class SampleServiceImpl implements SampleService {
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public List<TestLog> txTest5() throws Exception {
        // 데이터 저장 READWRITE
        sampleService3.test2();
        // 데이터 저장 READWRITE (REQUIRES_NEW 새로운 트랜잭션)
        // test3의 경우 새로운 트랜잭션이기 때문에 데이터가 롤백되지 않음.
        sampleService3.test3();
        throw new RuntimeException("강제 오류 발생");
    }
}
...
public class SampleServiceImpl3 implements SampleService3 {
    private final SampleTrxMapper sampleTrxMapper;

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void test2() {
        TestLog test = new TestLog();
        test.setSeq(1);
        test.setLog(getValue());
        test.setTestValue(getValue());
        sampleTrxMapper.insertTestLog(test);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = {Exception.class})
    public void test3() {
        TestLog test = new TestLog();
        test.setSeq(1);
        test.setLog(getValue());
        test.setTestValue(getValue());
        sampleTrxMapper.insertTestLog(test);
    }
}
```

위 예에서는 `test3()`이 `REQUIRES_NEW`로 별도의 트랜잭션이므로, 외부에서 예외가 발생해도 `test3()`의 저장 데이터는 rollback되지 않음. 반대로 `test2()`의 데이터는 외부 트랜잭션의 영향을 받아 rollback될 수 있음.

{% hint style="info" %}

### @Transactional 사용 시 주의사항

1. **@Transactional(readOnly=true) 동작**

@Transactional(readOnly=true) 일때 ReadWrite 데이터베이스에 연결되지만 ReadOnly로 작동됩니다.

* 내부적으로 Connection.setReadOnly(true)가 호출됩니다.

2. **readOnly 속성의 기본값 및 JPA와의 관계**

   * readOnly 속성의 기본값은 false 입니다.
   * JPA의 영속성 Context 내에서 readOnly=true 일때, Commit 시 Entity의 변경감지(Dirty Checking)를 수행하지 않으므로 성능상 이점을 얻을 수 있습니다.
   * 따라서 ReadWrite DB를 대상으로 JPA Entity를 조회할 때 readOnly=true를 사용합니다.
   * 코드 가독성을 위해 readOnly=false/true 속성은 명시해주길 권장합니다.

3. **@Transactional의 적용 대상 및 한계**

* @Transactional을 클래스 또는 메서드 레벨에 명시하면 해당 메서드 호출 시 지정된 트랜잭션이 작동합니다.
* 단, 조건이 있는데, 해당 클래스의 Bean을 다른 클래스의 Bean에서 호출할 때만 @Transactional을 인지하고 작동합니다.
* 같은 빈 내에서 @Transactional이 명시된 다른 메서드를 호출해도 작동하지 않습니다.
* 이유: Spring Framework는 내부적으로 AOP를 통해 해당 어노테이션을 인지하여 프록시를 생성하여 트랜잭션을 자동 관리하기 때문입니다.

이것을 해결하기 위한 방법으로는 다음 두 가지가 있습니다.

1. 구조 변경: ReadWrite, ReadOnly의 함수들을 2개의 서비스로 분리하여, 각각의 서비스가 호출될 때마다 AOP가 동작하도록 해서 각각의 서비스에 맞는 ReadWrite / ReadOnly 커넥션 풀을 설정하도록 합니다.
2. 지연조회: ObjectProvider 등을 사용하여, 실제 코드가 동작할 때 해당 인스턴스를 지연 조회하여 AOP가 동작하도록 합니다.

아래는 지연조회(ObjectProvider)를 사용하는 예제 코드입니다.

```
@Service
@Slf4j
@RequiredArgsConstructor
public class SampleServiceImpl implements SampleService {

  private final ObjectProvider<SampleServiceImpl> sampleServiceObjectProvider;      

  @Override
  @Transactional(rollbackFor = {Exception.class})
  public List<TestLog> txTest13() throws Exception {  

       // 데이터 저장 READWRITE  
       sampleService3.test2();

       // 같은 서비스내의 함수를 호출할 경우 트랜잭션 경계 설정이 작용하지 않음.  
       testInsert();  

       throw new RuntimeException("강제 오류 발생");  
  }

  @Override
  @Transactional(rollbackFor = {Exception.class})
  public List<TestLog> txTest14() throws Exception {

       // 데이터 저장 READWRITE  
       sampleService3.test2();

       // ObjectProvider를 사용하여 지연조회 방법을 사용할 경우 트랜잭션 경계 설정이 작용하지만,  
       // 스프링에서는 서비스를 분리하는 구조변경 방법을 지향함.  
       SampleService sempleService = sampleServiceObjectProvider.getObject();
       sempleService.testInsert();

       throw new RuntimeException("강제 오류 발생");  
  }

  @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = {Exception.class})
  public void testInsert() {

       TestLog test = new TestLog();
       test.setSeq(1);
       test.setLog(getValue());
       test.setTestValue(getValue());
       sampleTrxMapper.insertTestLog(test);
  }

}
```

위 예에서 txTest13() 함수를 호출하였을 때는 같은 서비스 내의 함수를 호출했기 때문에 트랜잭션 경계 설정이 작동하지 않아 testInsert()의 REQUIRES\_NEW 옵션이 적용되지 않고 모든 데이터가 rollback 됩니다.

그러나 txTest14() 함수의 경우 지연조회 방법인 ObjectProvider를 사용하였기 때문에 같은 서비스 내의 함수를 호출하더라도 REQUIRES\_NEW 옵션의 트랜잭션 경계가 제대로 적용되어 test2()의 데이터는 rollback 되지만 testInsert()의 데이터는 정상적으로 저장됩니다.

**스프링내에서는 b의 지연조회 방법보다는 a의 구조변경 방식을 지향합니다.**<br>
{% endhint %}

## 다중 데이터 소스 연결하기

서로 다른 DB 스키마 및 TransactionManager를 사용한 다중 데이터소스 트랜잭션 연결 방법(예: api-member에서 orderrwdb와 drmcrwdb 간 @Transactional rollbackFor 사용방법 및 설명)

서비스 예:

```java
@Service
@Slf4j
@RequiredArgsConstructor
public class SampleServiceImpl implements SampleService {
    @Transactional(value = "chainedTransactionManager", rollbackFor = {Exception.class})
    public void rollbackFor4(Test test) throws Exception {
        try {
            insertSample1(test); // orderrwdb insert
            insertSample2(test); // drmcrwdb insert
            throw new Exception("강제 오류 발생");
        } catch (Exception e) {
            throw AppException.exception(ApiError.FAIL_ERROR_INTEGRATE_WITHDRAWAL);
        }
    }
}
```

`@Transactional` 사용 시 `value = "chainedTransactionManager"`를 지정하여 사용합니다.

ChainedTransactionManager 구성 예:

```java
import org.springframework.data.transaction.ChainedTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

public class ChainedTransactionConfig {
    @Bean
    @Primary
    public PlatformTransactionManager chainedTransactionManager(
        @Qualifier("drmcRwdbTxManager") PlatformTransactionManager firstTxManager,
        @Qualifier("orderRwdbTxManager") PlatformTransactionManager secondTxManager) {
        return new ChainedTransactionManager(firstTxManager, secondTxManager);
    }
}
```

* `ChainedTransactionManager`는 org.springframework.data(Spring Data Commons)에서 제공하는 방식으로, 여러 트랜잭션 매니저를 하나로 묶어(Start/Commit을 순차 수행) 사용함으로써 하나의 트랜잭션처럼 동작하게 함.
* 다만 `ChainedTransactionManager`는 '완벽한' 트랜잭션을 제공하지 않으며, 에러 영향도가 큰 트랜잭션을 체인의 뒤쪽에 배치하는 등 주의가 필요함.

***

##


# 유효성 체크

**X2BEE 솔루션**에서 **Zod 라이브러리**를 사용해 데이터 유효성을 검증합니다. 본 가이드는 스키마 정의, 필드별 유효성 검증, 전체 데이터 검증, 데이터 가공 및 타입 추론을 효율적으로 구현하는 방법을 설명합니다.

***

{% stepper %}
{% step %}

### Zod 스키마 정의 방법

Zod는 TypeScript를 우선으로 하는 스키마 선언 및 유효성 검증 라이브러리이며, 중복된 유형 선언을 제거하기 위해 사용됩니다. Zod를 통해 유효성 검사를 하고 TypeScript 유형을 추론할 수 있습니다.

{% code title="예시: 기본 객체 스키마" %}

```javascript
import { z } from 'zod'

/** 기본적인 zod 객체 선언은 내부에 object 함수를 이용하여 객체를 생성하고 반환해줄 수 있습니다.
    Argument로 객체를 던져주면 상수 형태의 zod schema를 사용할 수 있습니다.
    Argument로 전달하는 객체의 경우 각 필드를 정의하고 타입을 zod의 타입으로 정의해줘야 합니다.
**/
const zodExampleSchema = z.object({
  name: z.string(),
  userId: z.string(),
  password: z.string(),
  phone: z.number()
})
```

{% endcode %}
{% endstep %}

{% step %}

### 각 필드별 유효성 체크 방법

Zod만으로는 일부 자유도 높은 검증에 제한이 있으므로, 각 항목에 대해 refine를 사용한 필드별 커스텀 검증을 적용할 수 있습니다. 아래는 빈 값 체크 등 기본적인 검증 예시입니다.

{% code title="예시: refine를 이용한 필드 검증" %}

```javascript
import { z } from 'zod'

/** min, max 등 다양한 기본 검증 방식들이 존재하지만,
    자유도 있는 검증을 위해 zod의 refine를 이용할 수 있습니다.
    refine의 첫번째 인자는 검증 통과 여부(boolean)를 반환하는 함수,
    두번째 인자는 메시지 등의 옵션입니다.
**/
const zodExampleSchema = z.object({
  name: z.string().refine(
    (data) => !!data, // data는 name 필드의 값을 의미 (boolean 반환)
    {
      message: "이름을 입력해주세요."
    }
  ),
  userId: z.string(),
  password: z.string(),
  phone: z.number()
})
```

{% endcode %}
{% endstep %}

{% step %}

### 전체 필드를 대상으로 한 유효성 체크 방법

필드 간 참조가 필요한 검증(예: 비밀번호와 비밀번호 확인 일치 여부)은 개별 필드의 refine로는 어려우므로, object 레벨에서 superRefine를 사용합니다. superRefine는 전체 데이터 객체와 컨텍스트를 받아 복합 검증을 수행할 수 있습니다.

{% code title="예시: superRefine를 이용한 전체 필드 검증" %}

```javascript
import { z } from 'zod'

/** superRefine는 z.object에 적용되는 함수로,
    첫번째 인자는 전체 필드 데이터를 가진 객체,
    두번째 인자는 zod의 context(ctx) 입니다.
**/
const zodExampleSchema = z.object({
  userId: z.string(),
  password: z.string(),
  rePassword: z.string()
}).superRefine((data, ctx) => {
  if (data.password !== data.rePassword) {
    ctx.addIssue({
      message: "비밀번호가 일치하지 않습니다.",
      code: z.ZodIssueCode.custom,
      path: ['password']
    })
    return false
  }
  return true
})
```

{% endcode %}
{% endstep %}

{% step %}

### 최종 스키마 데이터 가공 방법

검증 후 반환되는 데이터를 변환하거나 불필요한 필드를 제거하는 등 스키마 레벨에서 데이터를 가공하려면 transform을 사용합니다. transform은 검증이 통과된 후 최종값으로 변환된 객체를 반환합니다. 필요하다면 transform 전에 데이터를 가공하고 superRefine로 검증할 수도 있습니다.

{% code title="예시: superRefine + transform" %}

```javascript
import { z } from 'zod'

/** 아래 예시는 아이디 비어있음 체크, 비밀번호 확인 검사 후
    transform로 최종 반환값을 가공하는 예시입니다.
**/
const zodExampleSchema = z.object({
  userId: z.string().refine(
    (data) => !!data,
    { message: "아이디를 입력해주세요." }
  ),
  password: z.string(),
  rePassword: z.string()
}).superRefine((data, ctx) => {
  if (data.password !== data.rePassword) {
    ctx.addIssue({
      message: "비밀번호가 일치하지 않습니다.",
      code: z.ZodIssueCode.custom,
      path: ['password']
    })
    return false
  }
  return true
}).transform((data) => {
  return {
    userId: data.userId,
    password: data.password
  }
})
```

{% endcode %}
{% endstep %}

{% step %}

### 타입 추론 방법

Zod 스키마로부터 TypeScript 타입을 추론하려면 z.infer를 사용합니다.

{% code title="예시: z.infer로 타입 추론" %}

```javascript
import { z } from 'zod'

/** Typescript 변환 예시 **/
const zodExampleSchema = z.object({
  // ...스키마 정의
})

// Typescript type
type zodExampleType = z.infer<typeof zodExampleSchema>
```

{% endcode %}
{% endstep %}
{% endstepper %}


# 숫자 및 날짜 변환 가이드

X2BEE 솔루션에서 사용하는 숫자 및 날짜 변환 방법에 대한 가이드를 제공합니다. Front와 BO에서의 숫자 변환 지침과 날짜 변환 예제 및 예상 결과를 설명합니다.

***

## 숫자 변환 가이드

#### BO 숫자변환 가이드 (format-number.ts)

```ts
export function fNumber(inputValue: InputNumberValue, options?: Options) {
  const locale = formatNumberLocale() || DEFAULT_LOCALE
  const number = processInput(inputValue)
  if (number === null) return ''
  const fm = new Intl.NumberFormat(locale.code, {
    minimumFractionDigits: 0,
    maximumFractionDigits: 2,
    ...options
  }).format(number)
  return fm
}
```

샘플

* fNumber(24000000) — 결과: 24,000,000

***

## 날짜 변환 가이드

#### BO 날짜변환 가이드 (common-utils.ts)

```ts
/**
 * 날짜 변환
 * Format을 사용자 지정
 */
export const convertFormatDate = (value: ConfigType, formatValue: string) =>
  dayjs(value).format(formatValue)

/**
 * 날짜 변환 Type1
 * Format YYYY-MM-DD HH:mm:ss
 */
export const convertLocaleDateTime = (date: ConfigType) =>
  dayjs(date).format(DATE_FORMAT.LOCAL_DATE_TIME)

/**
 * 날짜 변환 Type2
 * Format YYYY-MM-DD
 */
export const convertLocaleDate = (date: ConfigType) =>
  dayjs(date).format(DATE_FORMAT.DEFAULT.DATE)
```

샘플

* convertFormatDate('2023-06-12T09:52:16', 'YYYY-MM-DD HH:mm');\
  결과: 2023-06-12 09:52
* convertLocaleDateTime('2023-06-12T09:52:16');\
  결과: 2023-06-12 09:52:16

{% hint style="info" %}

#### BO Locale

MUI 내에서 제공되는 `LocalizationProvider` 적용 및 `@mui/x-date-pickers` 컴포넌트를 사용합니다.

언어셋 변경 시, 변경된 언어셋에 해당되는 Date Format으로 자동 변환합니다.

`LocalizationProvider` 적용 방법은 다음 문서를 참고하세요:\
<https://mui.com/x/react-date-pickers/adapters-locale/>
{% endhint %}

***

#### BO 날짜 패턴 상수 (common-constants.ts)

```ts
export const DATE_FORMAT = {
  DATE_TIME: 'DD MMM YYYY h:mm a',
  DATE: 'DD MMM YYYY',
  TIME: 'h:mm a',
  LOCAL_DATE_TIME: 'YYYY-MM-DDTHH:mm:ss',
  SPLIT: {
    DATE_TIME: 'DD/MM/YYYY h:mm a',
    DATE: 'DD/MM/YYYY'
  },
  DEFAULT: {
    DATE_TIME: 'YYYY-MM-DD HH:mm:ss',
    DATE_TIME_WITH_MINUTE: 'YYYY-MM-DD HH:mm',
    DATE: 'YYYY-MM-DD',
    DOT_DATE: 'YYYY.MM.DD',
    DATE_STRING: 'YYYYMMDD',
    MONTH: 'YYYY-MM'
  },
  PARAM_CASE: {
    DATE_TIME: 'DD-MM-YYYY h:mm a',
    DATE: 'DD-MM-YYYY'
  }
}
```

샘플

* convertFormatDate('2023-06-12T09:52:16', DATE\_FORMAT.DEFAULT.DATE\_TIME\_WITH\_MINUTE);\
  결과: 2023-06-12 09:52
* convertFormatDate('2023-06-12T09:52:16', DATE\_FORMAT.DEFAULT.DATE\_TIME);\
  결과: 2023-06-12 09:52:16

***

#### 날짜 패턴 유형

아래 표현을 참고하여 format에 넣어 사용하시면 됩니다.

| 토큰   | 예시 출력            | 설명                                |
| ---- | ---------------- | --------------------------------- |
| YY   | 01               | Two-digit year                    |
| YYYY | 2001             | Four-digit year                   |
| M    | 1-12             | Month, beginning at 1             |
| MM   | 01-12            | Month, 2-digits                   |
| MMM  | Jan-Dec          | The abbreviated month name        |
| MMMM | January-December | The full month name               |
| D    | 1-31             | Day of month                      |
| DD   | 01-31            | Day of month, 2-digits            |
| H    | 0-23             | Hours                             |
| HH   | 00-23            | Hours, 2-digits                   |
| h    | 1-12             | Hours, 12-hour clock              |
| hh   | 01-12            | Hours, 12-hour clock, 2-digits    |
| m    | 0-59             | Minutes                           |
| mm   | 00-59            | Minutes, 2-digits                 |
| s    | 0-59             | Seconds                           |
| ss   | 00-59            | Seconds, 2-digits                 |
| S    | 0-9              | Hundreds of milliseconds, 1-digit |
| SS   | 00-99            | Tens of milliseconds, 2-digits    |
| SSS  | 000-999          | Milliseconds, 3-digits            |
| Z    | -05:00           | Offset from UTC                   |
| ZZ   | -0500            | Compact offset from UTC, 2-digits |
| A    | AM PM            | Post or ante meridiem, upper-case |
| a    | am pm            | Post or ante meridiem, lower-case |
| Do   | 1st...31st       | Day of Month with ordinal         |
| X    | 1410715640.579   | Unix timestamp (seconds)          |
| x    | 1410715640579    | Unix timestamp (ms)               |


# 메시지 처리 및 응답

## 개요

메시지 처리와 응답은 솔루션의 안정성과 사용자 경험에 중요한 영향을 미칩니다. 메시지 처리 및 응답은 에러메시지 및 예외처리, 응답값 Response 공통 처리, FO, BO Message 처리 등으로 구성됩니다. 각 항목별로 메시지 형식, 코드 정의, 예시 등에 대해 상세하게 안내합니다.

***

## 문서 구성

<details>

<summary><a href="/pages/52ce99daa815959ccc22e01dfb804f7feef6f869"><strong>에러메시지 및 예외 처리</strong> </a></summary>

X2BEE에서 발생하는 오류에 대한 효과적인 처리 방법을 자세히 설명합니다.

</details>

<details>

<summary><a href="/pages/27009024363c2dfb9360985e250a283b1a8ec86b"><strong>응답값 Response 공통 처리</strong></a> </summary>

모든 API 및 기능의 효율적이고 일관된 방식으로 응답 처리하기 위한 응답값의 형식, 구조, 상태 코드 등을 정의하고 공통 응답 처리 방법을 설명합니다.

</details>

<details>

<summary><a href="/pages/d1c7b6b86a66b7ca8aa8d90e8829e56594c28d6f"><strong>FO, BO Message 처리</strong> </a></summary>

FO(Front Office) 및 BO(Back Office)간의 효율적인 메시지 송수신을 위한 가이드라인과 예시를 제공합니다.

</details>


# 에러메시지 및 예외 처리

다음은 에러메시지 및 예외 처리에 대해 설명합니다.

***

## 예외 처리

서버에서 Exception이 발생되어 ‘예외 처리’를 하는 경우는 다음과 같습니다.

* 서버 페이지(Next.js)를 호출할 때 (BO 서버 프로젝트)
* Restful API를 호출할 때(API 서버 프로젝트)

일반적인 API서버들의 경우 Common에 있는 GlobalControllerAdvice 및 각각 API 프로젝트의 DisplayControllerAdvice(GlobalControllerAdvice를 상속) 등에서 예외를 처리합니다.

* **GlobalControllerAdvice.class&#x20;**<mark style="color:$danger;">**(해당 파일은 Common에서 작성되며, 이러한 Exception들을 처리하는 것으로 인식하면 됩니다.)**</mark>

GlobalControllerAdvice Class의 경우 공통에서 처리해야 될 예외 사항들을 처리합니다.

일반적인 Exception의 경우 Httpstatus값을 500으로 반환하고 있으며, ValidationException과 같이 400번대 오류들의 경우 앞에 9를 붙여서 9400, 9404, 9401, 9403등으로 반환합니다.

예시 소스(일부 발췌):

{% code title="GlobalControllerAdvice.java" %}

```java
/** * GlobalControllerAdvice */
@Slf4j
public class GlobalControllerAdvice {

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Object> handleException(Exception e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.error("", e);
        String code = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        String message = e.getMessage();
        int httpStatus = Integer.parseInt(code);
        ErrorCode errorCode = ErrorCode.builder()
            .code(code)
            .message(message)
            .httpStatus(httpStatus)
            .build();
        return handleExceptionInternal(errorCode);
    }

    @ExceptionHandler(BindException.class)
    protected ResponseEntity<Object> handleBindException(BindException e, HttpServletRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, this.attributeError);
        log.warn("", e);
        String code = CommonAppError.BINDING_ERROR.getCode();
        String message = getBindingErrorMessage(e.getBindingResult());
        String httpStatusCode = String.valueOf(HttpStatus.BAD_REQUEST.value());
        int httpStatus = getBadRequestHttpStatusCode(httpStatusCode);
        ErrorCode errorCode = ErrorCode.builder()
            .code(code)
            .message(message)
            .httpStatus(httpStatus)
            .build();
        return handleExceptionInternal(e, errorCode);
    }

    // ...... 그외 등등 소스코드가 길어서 생략함.
}
```

{% endcode %}

## 예외 반환 응답값

GlobalControllerAdvice에서 처리하는 Exception 목록 (Exception들은 대부분 추가 되었으나, 혹시 제외된 ‘예외 처리’가 있다면 추가될 수 있습니다.)

| Exception 종류                                                                                                                                                                                                                                                                                                                              | 반환응답값                        | 비고                                                                                                                                              |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Exception.class                                                                                                                                                                                                                                                                                                                           | 500                          | handleException 함수에서 처리하며, ControllerAdvice에서 잡지 못하는 모든 예외 사항은 해당 함수에서 처리함. 해당 Exception의 메시지값과 500 http status값을 반환함.                          |
| NullPointerException.class, IOException.class, ArrayIndexOutOfBoundsException.class, EntityNotFoundException.class, StringIndexOutOfBoundsException.class, IndexOutOfBoundsException.class, UnsupportedEncodingException.class                                                                                                            | 500                          | handleErrorException 함수에서 처리하며, 종류에 정의된 Exception들의 경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 500 http status값을 반환함.                                  |
| IllegalArgumentException.class, IllegalStateException.class, ConstraintViolationException.class, JsonParseException.class, com.fasterxml.jackson.core.JsonParseException.class, HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class, MissingServletRequestParameterException.class, MultipartException.class | 500                          | handleIllegalException 함수에서 처리하며, 종류에 정의된 Exception들의 경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 500 http status값을 반환함.                                |
| HttpInterfaceResponseException.class                                                                                                                                                                                                                                                                                                      | HttpInterface 에서 반환하는 응답값    | handleHttpInterfaceException 함수에서 처리하며, HttpInterface 기능에서 Exception이 날경우 해당 함수에서 처리함. HttpInterface를 호출하는 상대쪽에서 반환하는 메시지값과 http status값을 반환함.  |
| HttpException.class                                                                                                                                                                                                                                                                                                                       | RestApiInterface 에서 반환하는 응답값 | handleHttpException 함수에서 처리하며, RestApiInterface 기능에서 Exception이 날경우 해당 함수에서 처리함. RestApiInterface를 호출하는 상대쪽에서 반환하는 메시지값과 http status값을 반환함.     |
| WebClientResponseException.class                                                                                                                                                                                                                                                                                                          | WebClient에서 반환하는 응답값         | handleWebClientResponseException 함수에서 처리하며, WebClient 기능에서 Exception이 날경우 해당 함수에서 처리함. WebClient를 호출하는 상대쪽에서 반환하는 메시지값과 http status값을 반환함.      |
| WebClientRequestException.class                                                                                                                                                                                                                                                                                                           | 500                          | handleWebClientRequestException 함수에서 처리하며, WebClient 요청시 Exception이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 500 http status값을 반환함.                 |
| AsyncRequestTimeoutException.class                                                                                                                                                                                                                                                                                                        | 500                          | handleAsyncRequestTimeoutException 함수에서 처리하며, 비동기 요청시 Timeout Exception이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 500 http status값을 반환함.            |
| ValidationException.class                                                                                                                                                                                                                                                                                                                 | 원래 400이지만 9400으로 처리함. 9400   | handleValidationException 함수에서 처리하며, ValidationException이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9400 http status값을 반환함.                          |
| BindException.class                                                                                                                                                                                                                                                                                                                       | 원래 400이지만 9400으로 처리함. 9400   | handleBindException 함수에서 처리하며, BindException이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9400 http status값을 반환함.                                      |
| MethodArgumentNotValidException.class                                                                                                                                                                                                                                                                                                     | 원래 400이지만 9400으로 처리함. 9400   | handleMethodArgumentNotValidException 함수에서 처리하며, MethodArgumentNotValidException 이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9400 http status값을 반환함. |
| HttpRequestMethodNotSupportedException.class                                                                                                                                                                                                                                                                                              | 원래 405이지만 9405으로 처리함. 9405   | handleNotSupportedException 함수에서 처리하며, HttpRequestMethodNotSupportedException 이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9405 http status값을 반환함.    |
| NoHandlerFoundException.class                                                                                                                                                                                                                                                                                                             | 원래 404이지만 9404으로 처리함. 9404   | handleNoHandlerFoundException 함수에서 처리하며, NoHandlerFoundException 이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9404 http status값을 반환함.                 |
| MaxUploadSizeExceededException.class                                                                                                                                                                                                                                                                                                      | 원래 413이지만 9413으로 처리함. 9413   | handleMaxSizeException 함수에서 처리하며, MaxUploadSizeExceededException 이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9413 http status값을 반환함.                 |
| AuthenticationException.class                                                                                                                                                                                                                                                                                                             | 원래 401이지만 9401으로 처리함. 9401   | handleAuthenticationException 함수에서 처리하며, AuthenticationException 이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9401 http status값을 반환함.                 |
| JwtException.class                                                                                                                                                                                                                                                                                                                        | 원래 401이지만 9401으로 처리함. 9401   | handleJwtException 함수에서 처리하며, JwtException 이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9401 http status값을 반환함.                                       |
| AccessDeniedException.class                                                                                                                                                                                                                                                                                                               | 원래 403이지만 9403으로 처리함. 9403   | handleAccessDeniedException 함수에서 처리하며, AccessDeniedException 이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9403 http status값을 반환함.                     |
| ExpiredJwtException.class                                                                                                                                                                                                                                                                                                                 | 원래 403이지만 9403으로 처리함. 9403   | handleExpiredJwtException 함수에서 처리하며, ExpiredJwtException 이 날경우 해당 함수에서 처리함. 해당 Exception의 메시지값과 9403 http status값을 반환함.                         |

* **DisplayControllerAdvice.class**

(EventControllerAdvice 등등 각 API서버들의 ControllerAdvice Class)

<mark style="color:$danger;">(해당 파일은 각 프로젝트에서 작성되며 GlobalControllerAdvice를 상속받아서 AppException.class 만이 추가 되었습니다. 해당 Class 또한 특별하게 수정할 일이 없는 경우, 확인만 진행하면 됩니다.)</mark>

예시 소스(발췌):

{% code title="DisplayControllerAdvice.java" %}

```java
/** * DisplayControllerAdvice */
@RestControllerAdvice
@Slf4j
public class DisplayControllerAdvice extends GlobalControllerAdvice {

    @ExceptionHandler(AppException.class)
    protected ResponseEntity<Object> handleAppException(AppException e, WebRequest request) {
        RequestUtils.setAttribute(RequestLoggingFilter.REQUEST_LOG_LEVEL, "error");
        String code = Optional.ofNullable(e.getErrorCode()).orElse(ApiError.UNKNOWN.getCode());
        String message = e.getErrorMessage();
        int httpStatus = getAppExceptionHttpStatus(code);
        Boolean isProcess = Optional.ofNullable(e.getIsProcess()).orElse(false);
        log.warn("AppException: [{}] {}", code, message);
        log.warn("", e);
        ErrorCode errorCode = ErrorCode.builder()
            .code(code)
            .message(message)
            .httpStatus(httpStatus)
            .isProcess(isProcess)
            .build();
        return handleExceptionInternalValue(errorCode);
    }

    private int getAppExceptionHttpStatus(String code) {
        Integer httpStatus = null;
        if ("0000".equals(code)) {
            httpStatus = HttpStatus.OK.value();
        } else if ("9999".equals(code) || "9000".equals(code)) {
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR.value();
        } else if (code.indexOf("90") == 0) {
            String httpStatusCode = String.valueOf(HttpStatus.BAD_REQUEST.value());
            httpStatus = getBadRequestHttpStatusCodeValue(httpStatusCode);
        } else if (code.indexOf("91") == 0) {
            String httpStatusCode = String.valueOf(HttpStatus.UNAUTHORIZED.value());
            httpStatus = getBadRequestHttpStatusCodeValue(httpStatusCode);
        } else if (code.indexOf("93") == 0) {
            String httpStatusCode = String.valueOf(HttpStatus.FORBIDDEN.value());
            httpStatus = getBadRequestHttpStatusCodeValue(httpStatusCode);
        } else if (code.indexOf("94") == 0) {
            String httpStatusCode = String.valueOf(HttpStatus.NOT_FOUND.value());
            httpStatus = getBadRequestHttpStatusCodeValue(httpStatusCode);
        } else {
            String httpStatusCodeValue = String.valueOf(HttpStatus.BAD_REQUEST.value());
            httpStatus = getBadRequestHttpStatusCodeValue(httpStatusCodeValue);
        }
        return httpStatus;
    }
}
```

{% endcode %}

각각 API의 ControllerAdvice에서는 @ExceptionHandler(AppException.class)만을 담당하고 있습니다.

그리고 가장 중요한 getAppExceptionHttpStatus 함수에서는 반환할 Httpstatus값을 처리하고 있습니다.

## 프로젝트별 Code값 목록

프로젝트별 ControllerAdvice에서 처리하는 Code값 목록&#x20;

<mark style="color:$danger;">실제 업무단에서는 1000\~8999번대 code만 정의하기 때문에 하단 목록에서 가장 아래 항목인 ‘그외’만 확인하면 됩니다.</mark>

<table><thead><tr><th width="141.2222900390625">Code</th><th width="119.6666259765625">HttpStatus</th><th>설명 (ex)</th></tr></thead><tbody><tr><td>0000</td><td>200</td><td>SUCCESS("0000", "common.message.success", "common.message.success") — 0000인 경우에는 SUCCESS라고 판단하여 200으로 설정</td></tr><tr><td>9999</td><td>500</td><td>FAIL("9999", "common.message.fail", "common.message.fail") — 9999인 경우에는 FAIL라고 판단하여 500으로 설정</td></tr><tr><td>9000</td><td>500</td><td>UNKNOWN("9000", "common.error.unknown", "common.error.unknown") — 9000인 경우에는 UNKNOWN인데 역시나 500으로 설정</td></tr><tr><td>9001 ~ 9099</td><td>9400</td><td>여러 validation/parameter 관련 에러(예: EMPTY_PARAMETER, INVALID_PARAMETER 등). 400번대라고 판단하여 9를 붙여 9400으로 설정</td></tr><tr><td>9100 ~ 9199</td><td>9401</td><td>인증(401) 관련 코드들 — 9를 붙여 9401으로 설정</td></tr><tr><td>9300 ~ 9399</td><td>9403</td><td>권한(403) 관련 코드들 — 9를 붙여 9403으로 설정</td></tr><tr><td>9400 ~ 9499</td><td>9404</td><td>404 관련 코드들 — 9를 붙여 9404으로 설정</td></tr><tr><td>그외</td><td>9400</td><td>그외 모든 코드값들의 경우 현재는 모두 9를 붙여서 9400으로 설정. 실제로 각 업무단에서 1000~8999번대 코드들을 관리하게 됩니다. 해당 코드값들은 모두 http status값을 9400으로 반환합니다.</td></tr></tbody></table>

* **CommonAppError.class**

(Common에 있는 것으로 공통 0000, 9000번대 코드값들을 정의함)\ <mark style="color:$danger;">해당 Class는 공통에서 관리되기 때문에 이러한 Code값들이 관리가 되고 있는 수준만 확인하면 됩니다.</mark>

```
/**
 * CommonAppError
 */
@Getter
@AllArgsConstructor
public enum CommonAppError implements AppError {

	// --------- Common Code
	// OK 200
	SUCCESS("0000", "common.message.success", "common.message.success"),
	// INTERNAL_SERVER_ERROR 500
	FAIL("9999", "common.message.fail", "common.message.fail"),
	// INTERNAL_SERVER_ERROR 500
	UNKNOWN("9000", "common.error.unknown", "common.error.unknown"),

	// BAD_REQUEST 9400
	EMPTY_PARAMETER("9001", "common.error.emptyParameter", "common.error.emptyParameter"),
	INVALID_PARAMETER("9002", "common.error.invalidParameter", "common.error.invalidParameter"),
	INSERT_PARAMETER("9003", "common.error.insertParameter", "common.error.insertParameter"),
	DUPLICATE_DATA("9004", "common.error.duplicateData", "common.error.duplicateData"),
	INVALID_FILE("9005", "common.error.invalidFile", "common.error.invalidFile"),
	UPLOAD_FAIL("9006", "common.error.uploadFail", "common.error.uploadFail"),
	VALIDATION_EXCEPTION("9007", "common.error.validationParameter", "common.error.validationParameter"),
	BINDING_ERROR("9008", "common.error.bindingError", "common.error.bindingError"),
	BINDING_ERROR_NOT_NULL("9009", "common.error.bindingErrorNotNull", "common.error.bindingErrorNotNull"),

	// UNAUTHORIZED 9401
	REQUIRED_LOGIN("9100", "common.error.requiredLogin", "common.error.requiredLogin"),
	NEED_LOGIN("9101", "common.error.needLogin", "common.error.needLogin"), // 로그인 필요
	FAIL_DELETE_TOKEN("9102", "common.error.failDeleteToken", "common.error.failDeleteToken"),

	// FORBIDDEN 9403
	NOT_AUTHORIZED("9300", "common.error.notAuthorized", "common.error.notAuthorized"),
	DISPLAY_LIMIT("9301", "common.error.displayLimit", "common.error.displayLimit"),
	INVALID_TOKEN("9302", "common.error.invalidToken", "common.error.invalidToken"),
	FAIL_TOKEN("9303", "common.error.failToken", "common.error.failToken"),

	// NOT_FOUND 9404
	DATA_NOT_FOUND("9400", "common.error.dataNotFound", "common.error.dataNotFound");
	// Common Code ---------

	private final String code;
	private final String messageKey;
	private final String boMessageKey;

}
```

* **DisplayApiError.class**

(DisplayApiError등등 각 API서버들의 Error Class)

<mark style="color:red;">UI쪽에서 사용하는 isProcess 필드값이 추가가 되었습니다.</mark>

<mark style="color:red;">해당값은 기본값이 false이기 때문에 모든 메시지 값을 false로 쓸 경우, default 메서드가 false로 작동하기 때문에 다음과 같이 작성해주시면 됩니다.</mark>

```
@Getter
@AllArgsConstructor
public enum DisplayApiError2 implements AppError {

	EMPTY_PARAMETER("5000", "display.common.error.emptyParameter", "display.common.error.emptyParameter"),
    INVALID_PARAMETER("5001", "display.common.error.invalidParameter", "display.common.error.invalidParameter"),
    EMPTY_USER_DETAIL("5002", "display.common.error.emptyUserDetail", "display.common.error.emptyUserDetail");

	private final String code;
	private final String messageKey;
	private final String boMessageKey;

}
```

\ <mark style="color:red;">isProcess 필드값을 활용하시는 경우에는 다음과 같이 getIsProcess 함수를 Override하는 부분을 추가 해야 되며, 다음과 같이 마지막 항목에 false, true값 등을 모두 명시해 주셔야 합니다.</mark>

```
@Getter
@AllArgsConstructor
public enum DisplayApiError implements AppError {

	EMPTY_PARAMETER("5000", "display.common.error.emptyParameter", "display.common.error.emptyParameter", false),
    INVALID_PARAMETER("5001", "display.common.error.invalidParameter", "display.common.error.invalidParameter", true),
    EMPTY_USER_DETAIL("5002", "display.common.error.emptyUserDetail", "display.common.error.emptyUserDetail", false);

	private final String code;
	private final String messageKey;
	private final String boMessageKey;
	private final boolean isProcess;

	@Override
	public boolean getIsProcess() {
		return  isProcess;
	}

}
```

이렇게 각 서버 프로젝트들에서는 다음과 같이 사용할 Error값들을 <mark style="color:$danger;">1000 \~ 8999</mark>번대 코드로 정의해 주시면 됩니다.

&#x20;

※ BO프로젝트 같은 경우에는 타임리프와 같은 페이지 호출이 같이 있기 때문에 서버페이지를 호출할 때 Exception이 발생하면 ControllerAdvice Class에서 다음과 같은 분기 로직이 존재합니다.

```
if (isApiRequest(request)) {     
   //API 호출인 경우  ResponseEntity<Object>로 반환함.     
   return handleExceptionInternal(errorCode); 
}
//API 호출이 아닌 경우 error 페이지를 ModelAndView로 반환하여 에러페이지로 리디렉션 됩니다.
 return handleException(request, exception, object);
```

&#x20;

* **GlobalErrorController.java**

(BO같은 경우에는 error페이지로 보내는 경우가 존재함)

<mark style="color:red;">해당 Class파일은 BO 프로젝트에서만 존재합니다.</mark>

<mark style="color:red;">ControllerAdvice에서 api 호출이 아닌 경우에는 GlobalErrorController로 쪽으로 리디렉션 시켜서 error 페이지를 호출하는 정도입니다.</mark>

```
@Controller
@RequestMapping("/error")
@Slf4j
public class GlobalErrorController extends AbstractErrorController {
    public static final String EXCEPTION_KEY = "_ExceptioN_KEY_";

    public GlobalErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) // 2)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
        Map<String, Object> model = getErrorAttributes(request, options);
        String errorPage = "error/error";
        Exception exception = (Exception) request.getAttribute("jakarta.servlet.error.exception");

        if (exception != null) {
            Throwable throwable = exception.getCause();
            if (throwable instanceof AuthException) {
                errorPage = "error/403";
            } else if (throwable instanceof ValidationException) {
                errorPage = "error/404";
            } else {
                errorPage = "error/500";
            }
        } else {
            errorPage = "error/500";
        }

        ModelAndView modelAndView = new ModelAndView(errorPage, model);
        modelAndView.addObject(EXCEPTION_KEY, exception.getCause());
        return modelAndView;
    }

    @RequestMapping("loginExpired")
    public ModelAndView loginExpired(HttpServletRequest request, HttpServletResponse response) {
        ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
        Map<String, Object> model = getErrorAttributes(request, options);
        model.put("message", "로그인이 만료되었습니다.");
        ModelAndView modelAndView = new ModelAndView("error/loginExpired", model);
        return modelAndView;
    }

    protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {
        ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
        options = options.including(Include.MESSAGE);
        options = options.including(Include.BINDING_ERRORS);
        return options;
    }
    
    
    @RequestMapping
    public ResponseEntity<Response> error(HttpServletRequest request) {
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

        log.error("status_code: {}", request.getAttribute("jakarta.servlet.error.status_code"));
        log.error("exception_type: {}", request.getAttribute("jakarta.servlet.error.exception_type"));
        log.error("message: {}", request.getAttribute("jakarta.servlet.error.message"));
        log.error("request_uri: {}", request.getAttribute("jakarta.servlet.error.request_uri"));
        log.error("exception: {}", request.getAttribute("jakarta.servlet.error.exception"));

        Exception exception = (Exception) request.getAttribute("jakarta.servlet.error.exception");

        if (exception != null) {
            Throwable throwable = exception.getCause();
            if (throwable instanceof AuthException) {
                return new ResponseEntity<Response>(
                        Response.builder()
                                .code("0403")
                                .message(((AuthException) throwable).getMessage())
                                .error(true)
                                .build(),
                        new HttpHeaders(), HttpStatus.FORBIDDEN);
            } else {
                return new ResponseEntity<Response>(
                        Response.builder()
                                .code("9000")
                                .message(MessageResolver.getMessage("adminCommon.system.error"))
                                .error(true)
                                .build(),
                        new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
            }
        } else {
            HttpStatus httpStatus = null;

            if (status != null) {
                try {
                    httpStatus = HttpStatus.resolve(Integer.valueOf(String.valueOf(status)));
                } catch (Exception ex) {
                    httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
                }

                if (httpStatus == null) {
                    httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
                }

                if ("401".equals(status.toString())) {
                    httpStatus = HttpStatus.UNAUTHORIZED;
                    return new ResponseEntity<Response>(
                            Response.builder()
                                    .code("9000")
                                    .message(MessageResolver.getMessage("login.expried"))
                                    .error(true)
                                    .build(),
                            new HttpHeaders(), httpStatus);
                }

            } else {
                httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
            }

            return new ResponseEntity<Response>(
                    Response.builder()
                            .code("9000")
                            .message(MessageResolver.getMessage("adminCommon.system.error"))
                            .error(true)
                             .build(),
                    new HttpHeaders(), httpStatus);
        }
    }

}
```

## 에러메시지 처리 <a href="#undefined" id="undefined"></a>

에러메시지 처리의 경우 FO, BO Message 처리 부분인 해당 내용을 참고하면 됩니다.

Message값을 처리하기 위해서 공통에서 MessageResolver Class를 제공하고 있습니다.

#### MessageResolver.class <a href="#messageresolver.class" id="messageresolver.class"></a>

<kbd>getLocaleMessage</kbd>함수가 메시지값을 가져오는 주요 함수로서 다음과 같이 작동합니다.

1. messageKey값이 String Key 인자의 함수인 경우에는 그대로 메지시값 반환
2. messageKey값이 Emum Class 인자의 함수인 경우에는 RequestContextHolder 객체의 헤더값에서 호출한 서버명을 인식하여,\
   BO인 경우 2번째 Message 인자값인 boMessageKey의 메시지값을 반환,\
   호출한 서버명이 BO쪽이 아닌 경우 기존 messageKey 그대로 메시지값을 반환

사용법은 다음과 같습니다.

* **ApiError Class 파일 작성**

```
public enum ApiError implements AppError {
	// success
	SUCCESS("0000", "common.message.success", "common.message.success"),
	// app error
	EMPTY_PARAMETER("1001", "common.error.emptyParameter", "common.error.emptyParameter"),
	INVALID_PARAMETER("1002", "common.error.invalidParameter", "common.error.invalidParameter"),
	DATA_NOT_FOUND("1003", "common.error.dataNotFound", "common.error.dataNotFound"),
	DUPLICATE_DATA("1004", "common.error.duplicateData", "common.error.duplicateData"),
	INVALID_FILE("1005", "common.error.invalidFile", "common.error.invalidFile"),
	UPLOAD_FAIL("1100", "common.error.uploadFail", "common.error.uploadFail"),
	MEMBER_API_FAIL("1200", "common.error.memberApi", "common.error.memberApi"),
	
	EVENT_ENTRY_SUCCESS("2000", "event.entry.message.success", "event.entry.message.success"),
	EVENT_ERROR_EVENT_NOT_FOUND("2001", "event.error.eventNotFound", "event.error.eventNotFound"),
	EVENT_ERROR_SBSCCNTLMTCD_NOT_FOUND("2002", "event.error.sbscCntLmtCdNotFound", "event.error.sbscCntLmtCdNotFound"),
	EVENT_ERROR_EVENT_SBSC_IF_NOT("2003", "event.error.eventSbscIfNot", "event.error.eventSbscIfNot"),

	// unknow error
	UNKNOWN("9000", "common.error.unknown", "common.error.unknown"),
	// ValidatioException error
	VALIDATION_EXCEPTION("9100", "common.error.unknown", "common.error.unknown"),
	TEST("9999", "event.aply.simple.member.limit.message", "event.aply.simple.member.limit.message.bo"),
	TEST2("9999", "event.aply.simple.member.limit.message.bo", "event.aply.simple.member.limit.message.bo"),
	TEST3("9999", "event.aply.simple.member.limit.message2", "event.aply.simple.member.limit.message2.bo"),
	TEST4("9999", "event.aply.simple.member.limit.message2.bo", "event.aply.simple.member.limit.message2.bo");

	private final String code;
	private final String messageKey;
	private final String boMessageKey;
}
```

해당 enum class파일에 code값 및 FO message, BO message키값을 정의 합니다.

BO message값이 따로 없을 경우 FO message값을 동일하게 적용합니다.

* **message properties 파일 작성**

event\_ko.properties, event\_en.properties 등등 message properties파일을 정리합니다.

```
event.aply.simple.member.limit.message = 간편회원은 응모하실 수 없습니다.
event.aply.simple.member.limit.message.bo = 간편회원은 응모하실 수 없습니다.(BO)

event.aply.simple.member.limit.message2 = 간편회원은 응모하실 수 없습니다.22
```

해당 message properties파일에 FO 및 BO에서 사용할 메시지값을 정의함.

&#x20;

* **비지니스 로직에서 사용**

```
@GetMapping("/test")
public ResponseEntity<Response> test() throws Exception {
	
	// 메시지를 직접 가져오는 경우. 해당 메시지키값 그대로 반환함.
    String foMsg = MessageResolver.getMessage("event.aply.simple.member.limit.message");

    // 메시지를 직접 가져오는 경우. 해당 메시지키값 그대로 반환함.
	String boMsg = MessageResolver.getMessage("event.aply.simple.member.limit.message.bo");
	
	// 위와 동일하지만 정의한 ApiError enum Class를 활용하는 경우
	// 호출한 서버명에 따라서 messageKey값 또는 boMessageKey값을 반환함.
	String msg = MessageResolver.getMessage(ApiError.TEST);
	
	// AppException을 발생하는 경우
	// 호출한 서버명에 따라서 messageKey값 또는 boMessageKey값을 반환함.
	AppException.exception(ApiError.TEST);

	return ResponseEntity.ok().body(Response.builder().payload("성공").build());
}
```


# 응답값(Response) 공통 처리

다음은 X2BEE 응답값 Response 공통 처리에 대해 설명합니다.

공통처리 설명과 설정값 및 커스텀 어노테이션 사용 방법과 예제입니다.

***

## 공통처리 설명

응답값 공통 처리는 다음과 같습니다.

* String, int, List, Map, Model 객체 등 → 공통 모델인 Response의 payload에 Set → Response 객체를 ResponseEntity에 포함하여 반환
* Response → Response 객체를 ResponseEntity에 포함하여 반환
* ResponseEntity 객체인 경우 그대로 반환

## 설정값으로 사용

설정값으로 사용할 경우 application.yml 파일을 이용합니다.

응답값을 공통 모델인 Response 모델로 공통 처리하는 설정은 기본값이 true로 설정되어 있습니다.

해당 설정을 사용하지 않고 싶을 경우에는 application.yml 파일에서 global.response.advice 설정을 false로 줍니다.

{% hint style="warning" %}
해당 설정값이 없더라도 코드상 기본값이 true로 되어 있으므로, 공통 처리를 비활성화하려면 반드시 application.yml에 아래 값을 명시적으로 false로 설정해야 합니다. 설정을 삭제하는 것으로는 비활성화되지 않습니다.
{% endhint %}

예시 (application.yml):

```yaml
global:
  response:
    advice: true
```

## 커스텀 어노테이션 사용

아래 두 개의 커스텀 어노테이션을 사용하여 특정 컨트롤러/핸들러에서 공통 응답 처리 동작을 제어할 수 있습니다.

* @EnableResponseBodyAdvice — 해당 핸들러에 대해 응답값 공통 처리를 강제로 적용
* @DisableResponseBodyAdvice — 해당 핸들러에 대해 응답값 공통 처리를 비활성화

{% stepper %}
{% step %}

### Enable 예제

@EnableResponseBodyAdvice를 사용한 예제:

{% code title="SampleController - enable 예제" %}

```
```

{% endcode %}

```java
public class SampleController {

    @EnableResponseBodyAdvice
    @GetMapping("/search2")
    public ResponseEntity<List<SampleResponse>> searchSamples2(@RequestBody Optional<SampleRequest> sampleRequest) {
        // 데이터와 함께
        log.info("sampleRequest: {}", sampleRequest.isPresent() ? sampleRequest.get() : "");
        List<SampleResponse> data = sampleService.searchSamples();
        return ResponseEntity.ok().body(data);
    }
}
```

설명: @EnableResponseBodyAdvice을 사용할 경우 설정값이 false이더라도 어노테이션을 따라 응답값 공통 처리가 적용되어 최종적으로 Response 형태로 반환됩니다.
{% endstep %}

{% step %}

### Disable 예제

@DisableResponseBodyAdvice를 사용한 예제:

{% code title="SampleController - disable 예제" %}

```
```

{% endcode %}

```java
public class SampleController {

    @DisableResponseBodyAdvice
    @GetMapping("/search3")
    public ResponseEntity<List<SampleResponse>> searchSamples3(@RequestBody Optional<SampleRequest> sampleRequest) {
        // 데이터와 함께
        log.info("sampleRequest: {}", sampleRequest.isPresent() ? sampleRequest.get() : "");
        List<SampleResponse> data = sampleService.searchSamples();
        return ResponseEntity.ok().body(data);
    }
}
```

설명: @DisableResponseBodyAdvice을 사용할 경우 설정값이 없거나 true이더라도 어노테이션을 따라 응답값 공통 처리가 되지 않아 List 형태로 반환됩니다.
{% endstep %}
{% endstepper %}


# BO/API 메시지 처리

X2BEE BO(BackOffice)에서 메시지와 API의 메시지 처리 방법에 대해 설명합니다. 이를 통해 다국어 번역 처리 및 메시지 출력, 그리고 API에서 메시지를 동적으로 처리하는 방식에 대한 이해를 돕고, 실제 개발 환경에서의 활용 예시를 제공합니다.

***

## BO 메시지 처리 방식 가이드

BO의 메시지는 ‘다국어 번역’ 및 ‘사용자 경험 개선’을 위해 설계되었습니다. 이 구조는 `react-i18next`와 상태 관리 라이브러리를 활용하여 동적이고 일관된 번역과 메시지 출력을 제공합니다.

### allLangs 설정

다국어 번역 구조를 초기 설정하는 내용으로, 각 언어에 대한 기본 설정을 정의합니다.

<pre class="language-javascript"><code class="lang-javascript"><strong>export const allLangs = [
</strong>  {
    value: 'en',
    label: 'English',
    countryCode: 'GB',
    adapterLocale: 'en',
    numberFormat: { code: 'en-US', currency: 'USD' },
    systemValue: { components: { /* 영어 설정 */ } },
  },
  {
    value: 'ko',
    label: 'Korea',
    countryCode: 'KR',
    adapterLocale: 'ko',
    numberFormat: { code: 'ko', currency: 'WON' },
    systemValue: { components: { /* 한국어 설정 */ } },
  },
]
</code></pre>

### useTranslate Hook

다국어 번역을 관리하는 커스텀 훅으로, 언어 변경 처리 및 메시지 동기화 작업을 진행합니다.

<pre class="language-javascript"><code class="lang-javascript"><strong>export function useTranslate(
</strong>  ns?: string | string[], // 사용할 네임스페이스(다국어 번역 키 그룹)
  options: { keyPrefix?: KeyPrefix&#x3C;string> } = {} // 키 프리픽스 설정
) {
  const router = useRouter();
  // react-i18next의 번역 함수 및 언어 정보
  const { t, i18n } = useTranslation(ns, options);
  // 공통 코드 동기화 함수
  const { updateCodeList } = useUpdateCommonCodeStore();

  // 기본 언어 설정
  const fallback = allLangs.find((lang) => lang.value === fallbackLng);
  const currentLang = allLangs.find(
    (lang) => lang.value === i18n.resolvedLanguage // 현재 설정된 언어 확인
  );

  const onChangeLang = useCallback(
    async (newLang: LanguageValue) => {
      try {
        // 언어 쿠키 저장
        setCookie('data_lang_cd', newLang);
        setCookie('lang_cd', newLang);
        // 언어 변경 시 공통 코드 동기화
        updateCodeList();
        // 언어 변경 처리
        const langChangePromise = i18n.changeLanguage(newLang);
        // 해당 언어 메시지 가져오기
        const currentMessages = messages[newLang] || messages.en;
        // 언어 변경 상태에 따른 사용자 피드백
        toast.promise(langChangePromise, {
          loading: currentMessages.loading,
          success: () => currentMessages.success,
          error: currentMessages.error,
        });
        // 날짜 로케일 동기화
        if (currentLang) dayjs.locale(currentLang.adapterLocale);
        // 페이지 새로고침으로 UI 갱신
        router.refresh();
      } catch (error) {
        console.error(error);
      }
    },
    [currentLang, i18n, router, updateCodeList]
  );

  return {
    t, // 번역 함수
    i18n, // 번역 엔진 상태
    onChangeLang, // 언어 변경 핸들러
    currentLang: currentLang ?? fallback, // 현재 언어
  };
}
</code></pre>

### 메시지 JSON 작성

메시지 값은 `src/Locals/langs` 폴더 내 각 언어별 JSON 파일에 작성됩니다.

```json
{
  "adminCommon": {
    "message": {
      "successfully": {
        "saved": "저장되었습니다.",
        "deleted": "삭제되었습니다."
      }
    }
  }
}
```

### 페이지 및 컴포넌트에서의 사용 예시

페이지와 컴포넌트에서 다국어 메시지를 사용하는 예시입니다.

```javascript
const CM_NS = 'common';
const CM = { ns: CM_NS, keyPrefix: 'adminCommon' };

const sampleComponents = () => {
  const { t } = useTranslate([CM_NS]);
  const { dialogAlert } = useDialogContext();

  const onSave = () => {
    dialogAlert({
      text: t('message.successfully.saved', CM) // 저장되었습니다.
    });
  };

  // ...
};

export default sampleComponents;
```

***

## API 메시지 처리 방식 가이드

X2BEE는 API에서의 메시지 처리 방법으로 **MessageResolver 클래스**를 제공합니다. 이를 통해 서버에서 필요한 메시지를 동적으로 처리하고, 호출한 서버의 상황에 맞게 메시지를 반환합니다.

### MessageResolver.class

`getLocaleMessage` 함수는 메시지 키를 기반으로 메시지를 반환합니다. 서버가 BO-API인 경우, 해당 API에서 메시지를 반환하고, 그렇지 않으면 기본 메시지를 반환합니다.

```java
private static String getLocaleMessage(AppError appError, Object[] args, Locale locale) {
    String message = "";
    if (RequestContextUtil.isCallServerBo()) {
        // BO-API 호출 시
        message = getMessageKeyToMessageValue(appError.getBoMessageKey(), args, locale, false);
        if (StringUtils.isBlank(message)) {
            message = getMessageKeyToMessageValue(appError.getMessageKey(), args, locale, true);
        }
    } else {
        message = getMessageKeyToMessageValue(appError.getMessageKey(), args, locale, true);
    }
    return message;
}
```

### ApiError Class 파일 작성

해당 enum class 파일에 code 값 및 message 키 값을 정의하고, 서버에서 반환할 메시지 키를 관리합니다.

```java
public enum ApiError implements AppError {
    /* success */
    SUCCESS("0000", "common.message.success", "common.message.success", false),

    /* app error */
    EMPTY_PARAMETER("1001", "common.error.emptyParameter", "common.error.emptyParameter", false),
    INVALID_PARAMETER("1002", "common.error.invalidParameter", "common.error.invalidParameter", false),

    // unknown error
    UNKNOWN("9000", "common.error.unknown", "common.error.unknown"),

    // ValidationException error
    VALIDATION_EXCEPTION("9100", "common.error.unknown", "common.error.unknown"),

    TEST("9999", "event.aply.simple.member.limit.message", "event.aply.simple.member.limit.message.bo");

    private final String code;
    private final String messageKey;
    private final String boMessageKey;
    // ...
}
```

### message properties 파일 작성

`event_ko.properties`, `event_en.properties` 등 각 언어별로 메시지 값을 정의합니다.

```
event.aply.simple.member.limit.message = 간편회원은 응모하실 수 없습니다.
event.aply.simple.member.limit.message2 = 간편회원은 응모하실 수 없습니다.
```

### 비즈니스 로직에서 사용 예시

컨트롤러 또는 서비스에서 MessageResolver를 사용하는 예시입니다.

```java
@GetMapping("/test")
public ResponseEntity<Response> test() throws Exception {
    // 메시지를 직접 가져오는 경우. 해당 메시지 키 값 그대로 반환함.
    String eventMsg = MessageResolver.getMessage("event.aply.simple.member.limit.message");

    // 위와 동일하지만 정의한 ApiError enum Class를 활용하는 경우
    // 호출한 서버명에 따라서 messageKey 값을 반환함.
    String msg = MessageResolver.getMessage(ApiError.TEST);

    // AppException을 발생하는 경우
    // 호출한 서버명에 따라서 messageKey 값을 반환함.
    AppException.exception(ApiError.TEST);

    Response body = Response.builder().payload("OK").message(eventMsg).build();
    return ResponseEntity.ok().body(body);
}
```


# 보안 및 통신

## 개요

핵심적인 고려 사항 중 하나인 보안 및 통신에 대해 설명합니다.

로깅 설정, 클라이언트 및 서버 간 통신 데이터 처리, 서버 측 개발 방법, 데이터 암호화 등이 있습니다.

각 기능별로 구현 원리, 사용 라이브러리, 설정 파일, 테스트 방법 등에 대해 상세하게 안내합니다.

***

## 문서 구성

<details>

<summary><a href="/pages/f347274094f2fe43167d5db2de0a0c8d7781cbaf"><strong>로깅 설정</strong> </a></summary>

솔루션 운영 중 발생하는 이벤트 및 정보를 기록하는 디버깅 도구로 로깅 설정 및 로그 관리 방법에 대해 설명합니다.

</details>

<details>

<summary><a href="/pages/78e29190b307235fa3b811c9e9a15ebe794bc5bd"><strong>클라이언트 및 서버 간 통신 데이터 처리</strong> </a></summary>

효율적이고 안전한 데이터 처리 방법에 대해서 설명합니다.

</details>

<details>

<summary><a href="/pages/93a78388fb836ece42bca088ea8162fcb089673e"><strong>서버 측 개발 방법</strong> </a></summary>

서버 측 개발의 방법 및 권장 사항을 안내합니다.

</details>

<details>

<summary><a href="/pages/6be77d15a8ec962f08a80a592146b78baed8b935"><strong>데이터 암호화</strong></a></summary>

데이터 안정성을 보장하기 위한 데이터 암호화의 원리, 방법 및 구현에 대해 설명합니다.

각 문서의 상세 내용은 X2BEE의 기술적인 세부 정보를 포함하고 있으며, 프로젝트 개발을 지원하기 위한 자세한 내용을 다루고 있습니다.

</details>

<br>


# 로깅 설정

이 문서는 로깅 설정에 대해 다룹니다.

로깅은 정보를 제공하는 일련 기록인 로그(Log)를 생성하도록 시스템을 작성하는 것을 말합니다.

아래에서는 쿼리로깅을 설정하는 파일 구조와 로깅 라이브러리인 logback 관련 설정 방법에 대해 설명합니다.

***

## 로깅 관련 프로퍼티 설정

* 로깅 파일 저장 위치 설정
* log4jdbc 설정 작성
* logback 설정 작성
* 시스템 콘솔에 찍히는 로그 설정

## 쿼리로깅 파일 구조

프로젝트의 리소스 폴더 하위에 다음과 같은 파일을 배치합니다:

* src/main/resources/log4jdbc.log4j2.properties — 쿼리로깅 log4jdbc 설정파일
* src/main/resources/loback-spring.xml — logback 관련 설정파일

예시 트리:

```
/src/main/resources
├─ log4jdbc.log4j2.properties
└─ loback-spring.xml
```

## 쿼리로깅 log4jdbc 설정 파일 (properties)

resource 폴더 하위에 아래 파일을 추가합니다.

파일 내용 예시:

```properties
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.dump.sql.maxlinelength=0
```

## logback 관련 설정

* 파일 위치: src/main/resources/{ } 폴더 하위에 loback-spring.xml 파일 생성 및 설정
* consoleAppender: 시스템 콘솔에 출력되는 로그 정보 설정

아래는 loback-spring.xml 파일의 전체 예시 내용입니다.

{% code title="loback-spring.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

    <springProperty scope="context" name="myappName" source="spring.application.name"/>

    <property name="MSG_FORMAT"
              value="%d{yyyy-MM-dd HH:mm:ss} [${myappName}] [%-5p] [%t] [%X{traceId},%X{spanId}] [%F::%M\(%L\)] [%X{requestURL}] : %m%n"/>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${MSG_FORMAT}</pattern>
        </layout>
    </appender>

    <property name="COLOR_MSG_FORMAT"
              value="%clr(%d{yyyy-MM-dd HH:mm:ss}){faint} %clr([%-5p]) [%X{requestURL}] %clr([%X{traceId},%X{spanId}]){magenta} %clr([%30.-30F::%-20.20M\(%4L\)]){cyan} %clr(:){faint} %m%n"/>

    <appender name="COLOR_STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${COLOR_MSG_FORMAT}</pattern>
        </layout>
    </appender>

    <springProfile name="local, default">
        <!-- log4jdbc 옵션 설정 -->
        <logger name="jdbc" level="OFF"/>
        <!-- 커넥션 open close 이벤트 로그로 남김 -->
        <logger name="jdbc.connection" level="OFF"/>
        <!-- SQL문만을 로그로 남기며, PreparedStatement일 경우 관련된 argument 값으로 대체된 SQL문이 보여짐 -->
        <logger name="jdbc.sqlonly" level="OFF"/>
        <!-- SQL문과 해당 SQL을 실행시키는데 수행된 시간 정보(milliseconds)를 포함 -->
        <logger name="jdbc.sqltiming" level="DEBUG"/>
        <!-- ResultSet을 제외한 모든 JDBC 호출 정보를 로그로 남김. 방대한 양의 로그 생성 -->
        <logger name="jdbc.audit" level="OFF"/>
        <!-- ResultSet을 포함한 모든 JDBC 호출 정보를 로그로 남김 -->
        <logger name="jdbc.resultset" level="OFF"/>
        <!-- SQL 결과 조회된 데이터의 table을 로그로 남김 -->
        <logger name="jdbc.resultsettable" level="DEBUG"/>

        <logger name="com.amazonaws" level="error"/>

        <logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager" additivity="false" level="off">
            <appender-ref ref="COLOR_STDOUT" />
        </logger>

        <logger name="com.x2bee.common" additivity="false" level="debug">
            <appender-ref ref="COLOR_STDOUT" />
        </logger>

        <logger name="com.x2bee.api" additivity="false" level="debug">
            <appender-ref ref="COLOR_STDOUT" />
        </logger>

        <root level="info">
            <appender-ref ref="COLOR_STDOUT" />
        </root>
    </springProfile>

    <springProfile name="dev, stg, qa, prd">
        <appender name="logbackTcp" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
            <destination>fluentd-svc.thm-mgmt:24220</destination>
            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <timestamp/>
                    <mdc />
                    <pattern>
                        <pattern> { "project": "${myappName}" } </pattern>
                    </pattern>
                    <logLevel/>
                    <context />
                    <threadName/>
                    <loggerName/>
                    <callerData/>
                    <message/>
                    <stackTrace/>
                </providers>
            </encoder>
        </appender>
    </springProfile>

    <springProfile name="dev, stg, qa">
        <logger name="com.x2bee.common" additivity="false" level="debug">
            <appender-ref ref="logbackTcp" />
        </logger>

        <logger name="com.x2bee.api" additivity="false" level="debug">
            <appender-ref ref="logbackTcp" />
        </logger>

        <root level="info">
            <appender-ref ref="logbackTcp" />
        </root>
    </springProfile>

    <springProfile name="prd">
        <logger name="com.x2bee.common" additivity="false" level="warn">
            <appender-ref ref="logbackTcp" />
        </logger>

        <logger name="com.x2bee.api" additivity="false" level="warn">
            <appender-ref ref="logbackTcp" />
        </logger>

        <root level="info">
            <appender-ref ref="logbackTcp" />
        </root>
    </springProfile>
</configuration>
```

{% endcode %}


# 클라이언트 및 서버 간 통신 데이터 처리

다음은 클라이언트, 서버간 통신 데이터 형식입니다.

***

Map 형태가 아닌 VO(Value Object) 로 데이터 통신하는 것을 기본으로 합니다.

사용자 정보 처리 관련 VO 예제는 아래와 같습니다.

**User.java**

{% code title="User.java" %}

```java
package com.x2bee.common.entity;

import java.util.Date;
import lombok.Data;

@Data
public class User {
    private String usrId;
    private String usrNm;
    private String usrGrp;
    private String useYn;
    private String mobileNo;
    private String phoneNo;
    private String email;
    private String pw;
    private Date pwModDt;
    private String mailReceivedYn;
    private String smsReceivedYn;
    private String pwMissCnt;
    private String pwInitYn;
    private Date lastLoginDt;
    private String lastLoginIp;
    private String accExpireYn;
    private String accLockYn;
    private String pwModRequireYn;
}
```

{% endcode %}


# 서버 측 개발 방법

이 문서는 서버 측 개발 방식에 대해서 다룹니다.\
서버에서의 개발 패턴과 디렉토리 구조를 설명하고, 각 클래스 작성 방법에 대해 설명합니다.

***

## Controller, Service, (APIController, service), Mapper 구조

서버는 Spring MVC 패턴 기반으로 구성합니다.\
데이터 처리를 위하여 Controller → Service → Mapper 구조로 진행합니다.\
MAS 구조로 개발되며 Front(Next.js)에서 API서버를 호출하여 DB서버와 통신하는 형태로 개발합니다.

아래는 프로그램 호출 순서입니다

http request → (mapping) → Controller → Service → ServiceImpl → APIController → Service → ServiceImpl → Mapper → XML(query) (BO서버) (API서버)

***

**디렉토리 구조**

<figure><img src="/files/s94Gb1hIdptDycc60Sd8" alt=""><figcaption></figcaption></figure>

***

## DTO 클래스 작성

* src/main/java 하위 해당 업무 폴더에 dto, entity 폴더 생성 후 작업합니다.
* 데이터 전달을 위하여 사용될 객체를 사전 정의합니다.

**Sample.java**

{% code title="Sample.java" %}

```java
package com.x2bee.api.bo.app.entity;

import javax.validation.constraints.NotNull;
import org.apache.ibatis.type.Alias;
import com.x2bee.common.base.entity.BaseCommonEntity;
import lombok.Getter;
import lombok.Setter;

@Alias("Sample")
@Getter
@Setter
public class Sample extends BaseCommonEntity {
    private static final long serialVersionUID = -5756700830219562201L;
    private Long id;
    @NotNull
    private String name;
    private String description;
}
```

{% endcode %}

***

## API Controller 클래스 작성

* API 서버의 Controller 작성
* Response 객체로 Return

**SampleController.java**

{% code title="SampleController.java" %}

```java
package com.x2bee.api.bo.app.controller.sample;

import java.util.List;
import javax.validation.Valid;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.x2bee.api.bo.app.dto.request.common.SampleMpicRequest;
import com.x2bee.api.bo.app.dto.request.sample.SampleRequest;
import com.x2bee.api.bo.app.dto.response.sample.SampleResponse;
import com.x2bee.api.bo.app.entity.Sample;
import com.x2bee.api.bo.app.service.sample.SampleService;
import com.x2bee.api.bo.base.advice.ApiError;
import com.x2bee.api.bo.base.annotation.IndInfoLog;
import com.x2bee.common.base.exception.AppException;
import com.x2bee.common.base.rest.Response;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@RequestMapping("/samples")
@Lazy
@Slf4j
@RequiredArgsConstructor
public class SampleController {

    private final SampleService sampleService;

    @GetMapping("")
    public Response<List<SampleResponse>> getAllSamples() {
        return new Response<List<SampleResponse>>().setPayload(sampleService.getAllSamples());
    }

    @GetMapping("/{id}")
    public Response<SampleResponse> getSample(@PathVariable Long id,
            @RequestHeader(value="test-header-key1", required = false) String testHeader1) {
        log.info("id: {}, testHeader1: {}", id, testHeader1);
        return new Response<SampleResponse>().setPayload(sampleService.getSample(id));
    }

    @GetMapping("/search")
    public Response<List<SampleResponse>> searchSamples(SampleRequest sampleRequest) {
        log.info("sampleRequest: {}", sampleRequest);
        return new Response<List<SampleResponse>>().setPayload(sampleService.searchSamples(sampleRequest));
    }

    @PostMapping("")
    public Response<String> registerSample(@RequestBody @Valid Sample sample) throws InterruptedException {
        log.info("sampleRequest: {}", sample);
        return new Response<String>();
    }

    @PutMapping("/{id}")
    public Response<String> saveSample(@PathVariable Long id, @RequestBody SampleRequest sampleRequest) {
        log.info("id: {}, sampleRequest: {}", id, sampleRequest);
        return new Response<String>();
    }

    @PatchMapping("/{id}")
    public Response<String> modifySample(@PathVariable Long id, @RequestBody SampleRequest sampleRequest) {
        log.info("id: {}, sampleRequest: {}", id, sampleRequest);
        return new Response<String>();
    }

    @DeleteMapping("/{id}")
    public Response<String> removeSample(@PathVariable Long id) {
        log.info("id: {}", id);
        return new Response<String>();
    }

    @GetMapping("/error")
    public Response<String> getError() {
        if (true) {
            AppException.exception(ApiError.UNKNOWN);
        }
        return new Response<String>();
    }

    /**
     * @deprecated void 유형으로 응답하면 안됩니다. Response<T> 유형으로 응답 해야합니다.
     */
    @PostMapping("/void")
    public void registerVod(@RequestBody SampleRequest sampleRequest) throws InterruptedException {
        log.info("sampleRequest: {}", sampleRequest);
    }

    @PostMapping("/display-samples")
    public Response<String> registerDisplaySample(@RequestBody SampleRequest sampleRequest) throws Exception {
        log.info("sampleRequest: {}", sampleRequest);
        return new Response<String>().setPayload(sampleService.registerDisplaySample(sampleRequest));
    }

    @GetMapping("/call-order")
    public Response<List<SampleResponse>> callOrder(SampleRequest sampleRequest) throws Exception {
        log.info("sampleRequest: {}", sampleRequest);
        return new Response<List<SampleResponse>>().setPayload(sampleService.callChain(sampleRequest));
    }

    @GetMapping("/infInfoLog")
    @IndInfoLog
    public Response<List<SampleResponse>> infInfoLog(SampleRequest sampleRequest) {
        log.info("sampleRequest: {}", sampleRequest);
        return new Response<List<SampleResponse>>().setPayload(sampleService.searchSamples(sampleRequest));
    }

    /**
     * 동영상 업로드 샘플.
     * 상품컨텐츠정보 등록 시 동영상이 업로드 되는 경우의 샘플임.
     * POST formData 로 업로드한다.
     */
    @PostMapping("/mpic")
    public Response<String> registerGoodsContInfoWithMpic(SampleMpicRequest sampleMpicRequest) {
        sampleService.registerGoodsContInfoWithMpic(sampleMpicRequest);
        return new Response<String>();
    }
}
```

{% endcode %}

***

## API Service 클래스 작성

**SampleService.java**

{% code title="SampleService.java" %}

```java
package com.x2bee.api.bo.app.service.sample;

import java.util.List;

import com.x2bee.api.bo.app.dto.request.common.SampleMpicRequest;
import com.x2bee.api.bo.app.dto.request.sample.SampleRequest;
import com.x2bee.api.bo.app.dto.response.sample.SampleResponse;

public interface SampleService {
    public List<SampleResponse> getAllSamples();
    public SampleResponse getSample(Long id);
    public List<SampleResponse> searchSamples(SampleRequest sampleRequest);
    String registerDisplaySample(SampleRequest sampleRequest) throws Exception;
    public List<SampleResponse> callChain(SampleRequest sampleRequest) throws Exception;
    public void registerGoodsContInfoWithMpic(SampleMpicRequest sampleMpicRequest);
}
```

{% endcode %}

실제 구현체

**SampleServiceImpl.java**

{% code title="SampleServiceImpl.java" %}

```java
package com.x2bee.api.bo.app.service.sample;

import java.util.List;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;

import com.x2bee.api.bo.app.dto.request.common.SampleMpicRequest;
import com.x2bee.api.bo.app.dto.request.sample.SampleRequest;
import com.x2bee.api.bo.app.dto.response.common.MpicResponse;
import com.x2bee.api.bo.app.dto.response.sample.SampleResponse;
import com.x2bee.api.bo.app.entity.PrGoodsContInfo;
import com.x2bee.api.bo.app.entity.StMpicMappInfo;
import com.x2bee.api.bo.app.repository.displayrodb.sample.SampleMapper;
import com.x2bee.api.bo.app.repository.displayrwdb.sample.SampleTrxMapper;
import com.x2bee.api.bo.app.service.common.MpicService;
import com.x2bee.api.bo.base.advice.ApiError;
import com.x2bee.common.base.exception.AppException;
import com.x2bee.common.base.rest.Response;
import com.x2bee.common.base.rest.RestApiUtil;
import com.x2bee.common.base.upload.AttacheFileKind;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@Lazy
@Slf4j
@RequiredArgsConstructor
public class SampleServiceImpl implements SampleService {

    private final SampleMapper sampleMapper;
    private final SampleTrxMapper sampleTrxMapper;
    private final RestApiUtil restApiUtil;
    private final MpicService mpicService;

    @Value("${app.apiUrl.display}")
    private String displayApiUrl;

    @Value("${app.apiUrl.order}")
    private String orderApiUrl;

    @Value("${sample.prop}")
    private String sampleProp;

    @Override
    public List<SampleResponse> getAllSamples() {
        log.debug("sample prop: {}", sampleProp);
        return sampleMapper.selectAllSamples();
    }

    @Override
    public SampleResponse getSample(Long id) {
        SampleResponse sampleResponse = sampleMapper.selectSampleById(id).orElse(null);
        if (sampleResponse == null) {
            AppException.exception(ApiError.DATA_NOT_FOUND);
        }
        return sampleResponse;
    }

    @Override
    public List<SampleResponse> searchSamples(SampleRequest sampleRequest) {
        return sampleMapper.selectSamples(sampleRequest);
    }

    @Override
    public String registerDisplaySample(SampleRequest sampleRequest) throws Exception {
        return restApiUtil.post(displayApiUrl+ "/api/display/samples", sampleRequest, new ParameterizedTypeReference<Response<String>>() {}).getPayload();
    }

    @Override
    public List<SampleResponse> callChain(SampleRequest sampleRequest) throws Exception {
        return restApiUtil.get(orderApiUrl+ "/api/order/samples/call-display", sampleRequest, new ParameterizedTypeReference<Response<List<SampleResponse>>>() {}).getPayload();
    }

    /**
     * 동영상 업로드 샘플 서비스
     */
    @Override
    public void registerGoodsContInfoWithMpic(SampleMpicRequest sampleMpicRequest) {
        // 파일 업로드
        MpicResponse mpicResponse = mpicService.uploadMpic(sampleMpicRequest.getFile1(), sampleMpicRequest.getDirection(), AttacheFileKind.GOODS);

        // 업무컨텐츠정보 등록
        PrGoodsContInfo prGoodsContInfo = insertPrGoodsContInfo(sampleMpicRequest, mpicResponse);

        // 동영상변환상태정보 등록
        registerMpicMappInfo(prGoodsContInfo, mpicResponse);
    }

    // 업무컨텐츠정보 등록 - 업무별 비지니스 로직 구현필요
    private PrGoodsContInfo insertPrGoodsContInfo(SampleMpicRequest sampleMpicRequest, MpicResponse mpicResponse) {
        PrGoodsContInfo prGoodsContInfo = new PrGoodsContInfo();
        prGoodsContInfo.setGoodsNo("TEST_001");
        prGoodsContInfo.setCmtTypCd("02");
        prGoodsContInfo.setCmtSerialNo("10"+RandomStringUtils.randomNumeric(6));
        prGoodsContInfo.setOptnCatNo("1000");
        prGoodsContInfo.setOptnNo("BLACK");
        prGoodsContInfo.setImgGbCd("T01");
        prGoodsContInfo.setBaseImgYn("N");
        prGoodsContInfo.setContFilePathNm(null);
        prGoodsContInfo.setContFileNm(mpicResponse.getFileNm());
        prGoodsContInfo.setTrnfTextCont("testTrnfTextCont");
        prGoodsContInfo.setSysRegId("FRONT");
        prGoodsContInfo.setSysModId("FRONT");
        sampleTrxMapper.insertPrGoodsContInfo(prGoodsContInfo);
        return prGoodsContInfo;
    }

    // 동영상변환상태정보 등록
    private void registerMpicMappInfo(PrGoodsContInfo prGoodsContInfo, MpicResponse mpicResponse) {
        StMpicMappInfo stMpicMappInfo = new StMpicMappInfo();

        // 원본경로명이 "/" 로 시작하도록 처리함.
        String s3OrigPathNm = StringUtils.startsWith(mpicResponse.getS3OrigPathNm(), "/") ? mpicResponse.getS3OrigPathNm() : "/" + mpicResponse.getS3OrigPathNm();
        stMpicMappInfo.setOrgPathNm(s3OrigPathNm);
        stMpicMappInfo.setTblNm("pr_goods_cont_info");
        stMpicMappInfo.setRef1Val(prGoodsContInfo.getGoodsNo());
        stMpicMappInfo.setRef2Val(prGoodsContInfo.getCmtTypCd());
        stMpicMappInfo.setRef3Val(prGoodsContInfo.getCmtSerialNo());
        stMpicMappInfo.setSysRegId("SAMPLE");
        stMpicMappInfo.setSysModId("SAMPLE");

        mpicService.registerMpicMappInfo(stMpicMappInfo);
    }
}
```

{% endcode %}

***

## Mapper 클래스 및 Query 작성

* src/main/java 하위 해당 업무 폴더에 repository/{db연결명} 폴더 생성 후 Mapper 인터페이스 작성

**SampleMapper.java**

{% code title="SampleMapper.java" %}

```java
package com.x2bee.api.bo.app.repository.displayrodb.sample;

import java.util.List;
import java.util.Optional;

import com.x2bee.api.bo.app.dto.request.sample.SampleRequest;
import com.x2bee.api.bo.app.dto.response.sample.SampleResponse;

public interface SampleMapper {
    public List<SampleResponse> selectAllSamples();
    public Optional<SampleResponse> selectSampleById(Long id);
    public List<SampleResponse> selectSamples(SampleRequest request);
}
```

{% endcode %}

* src/main/resources 하위에 해당 쿼리(XML) 작성
* namespace 에 위 Mapper 클래스 명시
* resultType 혹은 parameterType 에 데이터 객체 명시

**SampleMapper.xml**

{% code title="SampleMapper.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.x2bee.api.bo.app.repository.displayrodb.sample.SampleMapper">

  <sql id="sampleList">
    SELECT 1 as id, 'name1' as name, 'desc1' as description
    union all
    SELECT 2 as id, 'name2' as name, 'desc2' as description
    union all
    SELECT 3 as id, 'name3' as name, 'desc3' as description
  </sql>

  <!-- 전체 샘플 조회 -->
  <select id="selectAllSamples" resultType="sampleResponse">
    /* SampleMapper.selectAllSamples */
    <include refid="sampleList" />
  </select>

  <!-- 샘플 단건 조회 -->
  <select id="selectSampleById" parameterType="long" resultType="sampleResponse">
    /* SampleMapper.selectSampleById */
    select * from (
      <include refid="sampleList" />
    ) a where id = #{id}
  </select>

  <!-- 샘플 목록 조회 -->
  <select id="selectSamples" parameterType="sampleRequest" resultType="sampleResponse">
    /* SampleMapper.selectSamples */
    select * from (
      <include refid="sampleList" />
    ) a
    <where>
      <if test="id != null">
        and id = #{id}
      </if>
      <if test="name != null and name != ''">
        and name = #{name}
      </if>
      <if test="description != null and description != ''">
        and description = #{description}
      </if>
    </where>
  </select>

</mapper>
```

{% endcode %}

***

**위 작업 진행 시 최종 패키지 구조는 다음과 같습니다.**

<figure><img src="/files/js4r2cwJe7yidobCcy5U" alt=""><figcaption></figcaption></figure>


# 데이터 암호화

다음은 CryptoUtil 데이터 암호화 정의입니다.

암호화의 경우 기존 프로젝트에 사용하던 AwsCrypto 패키지를 삭제하면서 AwsCryptoUtil Class 파일을 삭제하고, 스프링에서 기본적으로 제공하는 **BCrypt 해싱함수**와 **AES256 Encode, Decode** 모듈을 제공하는 CryptoUtil Class 파일로 대체 하였습니다.

***

## 의존성 주입(DI, Dependency Injection) 사용 방법

AES256에서 사용할 32자리 Secret Key를 application.yml 파일에 `crypto.secret.key`에 설정함.

설정 하지 않을 경우 내부적으로 `defaultSecretKey` 값으로 `X2BEE_Application_DATA_SecretKey`을 사용함. ⚠️ 운영 환경에서는 반드시 application.yml(또는 Jasypt·보안 설정)에서 crypto.secret.key 를 배포 환경별 고유한 32자 키로 재정의해야 합니다. 기본키를 그대로 사용하면 암호화된 데이터가 노출될 위험이 있습니다.

**application.yml 예시:**

```yaml
crypto:
  secret:
    key: X2BEE_Application_DATA_SecretKey
```

사용할 Class 파일에서 생성자를 통해 Bean을 주입한 후 `encodeBcrypt`, `matchesBcrypt`, `encodeAes`, `decodeAes` 4개의 함수를 사용함.

**CryptoUtilTest1.java**

{% code title="CryptoUtilTest1.java" %}

```java
package com.x2bee.api.display;

import com.x2bee.common.base.util.CryptoUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;

@SpringBootTest
@AutoConfigureMockMvc
@Slf4j
class CryptoUtilTest1 {

    private CryptoUtil cryptoUtil;

    @Autowired
    public ApiTest(final CryptoUtil cryptoUtil) {
        Assert.notNull(cryptoUtil, "CryptoUtil can't be null");
        this.cryptoUtil = cryptoUtil;
    }

    @Test
    public void test1() throws Exception {
        log.info("------------------------------------- test1 START ----------------------------------------------------------");

        String value = cryptoUtil.encodeBcrypt("가나다");
        log.info(value);

        Boolean value2 = cryptoUtil.matchesBcrypt("가나다1", value);
        log.info("value2 : " + value2);

        Boolean value3 = cryptoUtil.matchesBcrypt("가나다", value);
        log.info("value3 : " + value3);

        String value4 = cryptoUtil.encodeAes("가나다");
        log.info(value4);

        String value5 = cryptoUtil.decodeAes(value4);
        log.info(value5);

        log.info("------------------------------------- test1 END ----------------------------------------------------------");
    }
}
```

{% endcode %}

***

## 싱글톤 패턴 사용 방법

싱글톤 인스턴스를 가져와서 위와 동일하게 함수 사용함.

**CryptoUtilTest2.java**

{% code title="CryptoUtilTest2.java" %}

```java
package com.x2bee.api.display;

import com.x2bee.common.base.util.CryptoUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@AutoConfigureMockMvc
@Slf4j
class CryptoUtilTest2 {

    @Test
    public void test1() throws Exception {
        log.info("------------------------------------- test1 START ----------------------------------------------------------");

        CryptoUtil.getInstance().setSecretKey("X2BEE_Application_DATA_SecretKey");

        String value = CryptoUtil.getInstance().encodeBcrypt("가나다");
        log.info(value);

        Boolean value2 = CryptoUtil.getInstance().matchesBcrypt("가나다1", value);
        log.info("value2 : " + value2);

        Boolean value3 = CryptoUtil.getInstance().matchesBcrypt("가나다", value);
        log.info("value3 : " + value3);

        String value4 = CryptoUtil.getInstance().encodeAes("가나다");
        log.info(value4);

        String value5 = CryptoUtil.getInstance().decodeAes(value4);
        log.info(value5);

        log.info("------------------------------------- test1 END ----------------------------------------------------------");
    }
}
```

{% endcode %}

***

## @Encrypt 커스텀 어노테이션 사용

`@Encrypt` 커스텀 어노테이션을 사용하여 MyBatis에서 저장 및 조회 시 자동으로 AES256 encode/decode 적용.

Common의 MyBatis AOP 모듈에서 `@Encrypt` 어노테이션이 있는 필드의 경우 저장 전에 `encodeAes` 함수를 실행하여 값을 encode하여 저장하고, 조회 시에는 DB 조회 후 `decodeAes` 함수를 실행하여 decode된 값을 모델에 설정함.

**TestLog.java**

{% code title="TestLog.java" %}

```java
package com.x2bee.api.display.app.dto.sample;

import com.x2bee.common.base.encrypt.Encrypt;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.ibatis.type.Alias;

@Alias("testLog")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class TestLog {
    private Integer seq;
    private String log;

    @Encrypt
    private String testValue;
}
```

{% endcode %}

***

## @Convert(converter = JpaEncryptor.class) 어노테이션 사용

`@Convert` 어노테이션은 JPA에서 제공하는 Convert 모듈로서, Convert 설정값에는 Common에 있는 `JpaEncryptor.class`를 설정함.

위 MyBatis AOP와 동일하게 JPA로 저장 및 조회 시 동일하게 자동으로 AES256 encode/decode 함수를 적용함.

**TestLogEntity.java**

{% code title="TestLogEntity.java" %}

```java
package com.x2bee.api.display.app.entity;

import com.x2bee.api.display.app.dto.sample.TestLog;
import com.x2bee.common.base.encrypt.JpaEncryptor;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicUpdate;

@Getter
@Setter
@Table(schema="public", name = "test_log")
@Entity
@NoArgsConstructor
@DynamicUpdate
public class TestLogEntity {

    @Id
    @Column(name = "seq", nullable = false)
    private Integer seq;

    @Column(name = "log", nullable = false)
    private String log;

    @Convert(converter = JpaEncryptor.class)
    @Column(name = "test_value", nullable = false)
    private String testValue;
}
```

{% endcode %}

***

## 사용 예시 (EncryptUtils)

다음은 EncryptUtils를 사용한 예시입니다.

**Sample.java**

{% code title="Sample usage" %}

```java
/* 암호화 Value */
EncryptUtils.getEncryptValue("Sample");
// 예상 결과값 : MW5NMo6yro63GMEalqAI0A==

/* 복호화 Value */
EncryptUtils.getDecryptValue("MW5NMo6yro63GMEalqAI0A==");
// 예상 결과값 : Sample
```

{% endcode %}


# 고객 메시지 전송

## 개요

고객과의 효과적인 커뮤니케이션을 위한 고객에게 메시지를 전송하는 방법에 대해 설명합니다.

고객 메시지 전송은 문자전송 가이드, 알림톡전송 가이드, 메일전송 가이드 등으로 구성됩니다.

각 항목별로 전송 방식, 사용 서비스, 설정 파일, 템플릿 작성 방법, 테스트 방법 등에 대해 상세하게 안내합니다.

***

## 문서 구성

<details>

<summary><a href="/pages/0e1e4a0830ff371b33bdbc89d866a902aec86d55"><strong>문자전송 가이드</strong></a> </summary>

고객에게 문자 보내기 위한 문자 전송 소스와 설정 방법에 대해 설명합니다.

</details>

<details>

<summary><a href="/pages/d5d1fb6c727ddcb2d862a815a1a38ae8d3632e4c"><strong>메일 전송 가이드</strong> </a></summary>

고객에게 메일 보내기 위한 메일 전송 소스와 방법에 대해 설명합니다.

</details>

<details>

<summary><a href="/pages/378b67b65cddb3fe5b462fdec85520c2668ba996"><strong>알림톡 전송 가이드</strong>  </a></summary>

고객에게 카카오톡으로 알림톡 메시지를 보내기 위한 소스와 방법에 대해 설명합니다.

</details>


# 문자메시지 발송

이 문서는 문자전송 소스와 방법에 대해 다룹니다.

첫 번째로는 Config 설정 방법 및 확인에 대해 설명합니다.

두 번째로 각 업무단 작성 방법을 설명합니다.

{% hint style="info" %}
**\<update>**<br>

2023.08.03

* MMS 이미지 업로드 관련 내용이 추가되었습니다.
* 문자 전송시 Api Common을 호출을 지향합니다.
  {% endhint %}

***

## Config 설정

**application.yml**

{% code title="application.yml" %}

```yaml
spring:
  bizMessage:
    accessKey: VHIZ4hLLrhf6uyRIrk4F
    secretKey: pHvcggk52DXYOYYUDvXyLi5RkcmwneeMoOx8xoTd
    sms:
      smsNum: T010-9341-7470 # 발신자 번호 T***-****-****
      serviceId: ncp:sms:kr:309485696868:x2bee
```

{% endcode %}

* `accessKey`, `secretKey`, `serviceId` 속성들은 NCP(Naver Cloud Platform) 에서 발급받아 작성합니다.
* `smsNum` 속성은 NCP(Naver Cloud Platform) 에 등록하여 허가받은 발신자 번호를 작성합니다.

## Sample 소스 및 설명

* [x2bee-api-sample-vanilla](https://gitlab.x2bee.com/x2bee-venus-beta/venus-x2bee-api-sample-vanilla) 프로젝트

**BizMessageController**&#x20;

{% code title="BizMessageController (핵심 발췌)" %}

```java
@Autowired
private MessageSender messageSender;

@PostMapping("/sendSms")
public ResponseEntity<Response> sendSms() throws Exception {
    ...
    List<MessagesRequest> messagesList = new ArrayList<>();
    for(int i=0;i<10;i++) {
        messagesList.add(MessagesRequest.builder()
            .receiverPhoneNumber("01012341234")
            .subject("LMS, MMS에서만 사용 가능한 제목") //BizMessageRequest.subject 보다 우선적용
            .content("X2BEE 테스트 플래티어님 환영합니다.") //BizMessageRequest.content 보다 우선적용
            .build());
    }

    // 이미지 업로드시
    String image1 = ".jpg, .jpeg 이미지를 Base64로 인코딩한 값, 파일 기준 최대 300Kbyte, 해상도 최대 1500 * 1440";
    String image2 = ".jpg, .jpeg 이미지를 Base64로 인코딩한 값, 파일 기준 최대 300Kbyte, 해상도 최대 1500 * 1440";

    // 이미지 파일은 최대3개까지만 전송 가능
    List<String> images = new ArrayList<>();
    images.add(image1);
    images.add(image2);
    images.add(image2);

    BizMessageRequest request = BizMessageRequest.builder()
        .messages(messagesList)
        .subject("기본 LMS, MMS에서만 사용 가능한 메시지 제목") //MessagesRequest.subject 에 값이 있으면 무시됨
        .content("기본 메시지 내용") //MessagesRequest.content 에 값이 있으면 무시됨
        .images(images)
        .type("SMS")
        .build();

    //======================================================
    // API-COMMON 호출
    BizMessageResponse response = restApiUtil.post(
        this.commonApiUrl + "/api/common/interface/bizmessage/sendsms",
        request,
        new ParameterizedTypeReference<Response<BizMessageResponse>>() {}
    ).getPayload();
    //======================================================
    // API-COMMON 에서 직접 호출시
    // messageSender.sendSms(request);
    ...
}
```

{% endcode %}

**BizMessageRequest** 를 생성하여 예제와 아래 표를 참조하여 파라미터를 입력합니다.

* **LMS, MMS 전송 시에는 아래 표의 비고란을 참조하여 작성합니다.**

| 항목                                  | Mandatory | Type   | 설명                                  | 비고                                                                                                                                                                                                                   |
| ----------------------------------- | --------- | ------ | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| BizMessageRequest.type              | NOT NULL  | String | SMS Type                            | SMS, LMS, MMS (소문자 가능)                                                                                                                                                                                               |
| BizMessageRequest.subject           | Optional  | String | 기본 메시지 제목                           | <p><strong>LMS, MMS에서만 사용 가능</strong></p><ul><li>LMS, MMS: 최대 40byte</li></ul>                                                                                                                                       |
| BizMessageRequest.content           | Optional  | String | 기본 메시지 내용                           | <ul><li>SMS: 최대 80byte</li><li>LMS, MMS: 최대 2000byte</li></ul>                                                                                                                                                       |
| BizMessageRequest.messages          | NOT NULL  | List   | 메시지 정보                              | <ul><li>아래 항목 참조 (<a href="http://messages.xxx/">messages.XXX</a>)</li><li>최대 100개</li></ul>                                                                                                                         |
| MessagesRequest.receiverPhoneNumber | NOT NULL  | String | 수신번호                                | 붙임표 ( - )를 제외한 숫자만 입력 가능                                                                                                                                                                                             |
| MessagesRequest.subject             | Optional  | String | 개별 메시지 제목 상위 subject를 무시하고 우선적용됩니다. | <p><strong>LMS, MMS에서만 사용 가능</strong></p><ul><li>LMS, MMS: 최대 40byte</li><li>상위 subject를 무시합니다.</li></ul>                                                                                                            |
| MessagesRequest.content             | NOT NULL  | String | 개별 메시지 내용 상위 content를 무시하고 우선적용됩니다. | <ul><li>SMS: 최대 80byte</li><li>LMS, MMS: 최대 2000byte</li><li>상위 content를 무시합니다.</li></ul>                                                                                                                            |
| BizMessageRequest.images            | Optional  | List   | 파일 바이너리를 Base64로 인코딩한 값             | <ul><li><code>.jpg</code>, <code>.jpeg</code> 이미지를 <strong>Base64</strong>로 인코딩한 값</li><li>파일 하나당 최대 300Kbyte</li><li>해상도 최대 1500 \* 1440</li><li>MMS 전송 용량 <strong>2000byte 이하</strong>까지 최대 3개 이미지 전송 가능</li></ul> |

{% hint style="info" %}
Builder가 익숙치 않다면 Setter 도 사용 가능합니다.
{% endhint %}

예시 (Setter 사용)

{% code title="Setter 예시" %}

```java
for(int i=0;i<10;i++) {
    MessagesRequest messagesRequest = new MessagesRequest();
    messagesRequest.setReceiverPhoneNumber("01012341234");
    messagesRequest.setContent("X2BEE 테스트 플래티어님 환영합니다.");
    messagesList.add(messagesRequest);
}
```

{% endcode %}

{% hint style="info" %}

### 다른 프로젝트에서 SMS 전송 시 (권장 방식)

(1) `x2bee-api-common-vanilla` 가 아닌 다른 프로젝트에서 SMS를 전송하는 경우 api-common API를 호출합니다.

예제 소스와 같이 `BizMessageRequest` 객체에 필요 파라미터들을 담아 POST 방식으로 `/api/common/interface/bizmessage/sendsms` End Point를 호출합니다.

* `messages` 와 `type` 변수는 필수 파라미터입니다.
* `commonApiUrl` 은 application.yml 에 명시된 URL 입니다.
  {% endhint %}


# 메일 발송

이 문서는 메일전송 소스와 방법에 대해 다룹니다.

첫 번째로는 Config 설정 방법 및 확인에 대해 설명합니다.\
두 번째로 각 업무단 작성 방법을 설명합니다.

{% hint style="info" %}
\<update>

2023.08.03

* MailSendRequest siteNo 를 입력하지 않아도 자동으로 넘어가도록 수정했습니다.

2024.05.29

* sendTemplateMailBulk 추가
  {% endhint %}

***

## Config 설정

**application.yml**

```yaml
mail:
  smtp:
    host: smtp.office365.com
    port: 587
    userName: x2bee@plateer.com
    password: X2commerce!1
  fromMail: x2bee@plateer.com
  fromNameKo: 플래티어_X2BEE
  fromNameEn: PLATEER_X2BEE
```

## Sample 소스 및 설명

* [x2bee-api-sample-vanilla](https://gitlab.x2bee.com/x2bee-venus-beta/venus-x2bee-api-sample-vanilla) 프로젝트

**MailController 예시 (요약):**

```java
@Autowired
private MailSender mailSender;

@PostMapping("/send")
public ResponseEntity<Response> send() throws MessagingException, UnsupportedEncodingException {
    Map<String, Object> params = new HashMap<>();
    // 템플릿용 파라미터
    params.put("name","teset");

    // 단건 전송
    MailSendRequest request = MailSendRequest.builder()
        .siteNo("1") // null 일 경우 Cookie에서 자동입력됩니다.
        .mbrNo("100033")
        .emailGbCd("EMA-MA_11")
        .emailTitle("안녕하세요 이메일 전송 테스트입니다.")
        .emailConts("이메일 내용이 들어가는 곳입니다.") // 템플릿을 사용하는경우 삭제!
        .template("sample/sp1") // 템플릿을 사용하는 경우 ---(2)필독!
        .variables(params) // 템플릿용 파라미터
        .recvmnNm("테스트")
        .recvmnEmailAddr("emailtempid@naver.com")
        .build();

    // ----- API-COMMON 프로젝트에서 직접 호출시
    mailSender.send(request);

    // ----- API-COMMON 호출시
    // restApiUtil.post(
    //   this.commonApiUrl + "/api/common/interface/bizmessage/sendmail",
    //   request,
    //   new ParameterizedTypeReference<Response<Void>>() {}
    // ).getPayload();

    // 대량 전송 예시
    List<MailSendRequest> mailSendRequestList = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        mailSendRequestList.add(
            MailSendRequest.builder()
                .siteNo("1") // null 일 경우 Cookie에서 자동입력됩니다.
                .mbrNo("100033")
                .emailGbCd("EMA-MA_11")
                .emailTitle("안녕하세요 이메일 전송 테스트 : "+(i+1))
                .emailConts("이메일 내용이 들어가는 곳입니다. -- "+(i+1)) // 템플릿을 사용하는경우 삭제!
                .template("sample/sp1") // 템플릿을 사용하는 경우 ---(2)필독!
                .variables(params) // 템플릿용 파라미터
                .recvmnNm("테스트")
                .recvmnEmailAddr("emailtempid@naver.com")
                .build()
        );
    }

    // ----- API-COMMON 프로젝트에서 직접 호출시
    // mailSender.sendBulk(mailSendRequestList);

    // ----- API-COMMON 호출시
    // restApiUtil.post(
    //   this.commonApiUrl + "/api/common/interface/bizmessage/sendmailbulk",
    //   mailSendRequestList,
    //   new ParameterizedTypeReference<Response<Void>>() {}
    // ).getPayload();

    ...
}
```

<kbd>MailSendRequest</kbd> 를 생성하여 예제와 아래 테이블을 참조하여 파라미터를 입력합니다.

| 항목              | Mandatory       | Type   | 설명             | 비고                  |
| --------------- | --------------- | ------ | -------------- | ------------------- |
| siteNo          | NOT NULL (자동입력) | String | 사이트 번호         | null 로 작성시 쿠키값 자동입력 |
| mbrNo           | NOT NULL        | String | 회원 번호          |                     |
| emailGbCd       | NOT NULL        | String | 이메일구분코드(CM030) |                     |
| emailTitle      | NOT NULL        | String | 이메일 제목         |                     |
| emailConts      | NOT NULL        | String | 이메일 내용         |                     |
| recvmnNm        | NOT NULL        | String | 수신자 성명         |                     |
| recvmnEmailAddr | NOT NULL        | String | 수신자 이메일 주소     |                     |

**Builder 가 익숙치 않다면 Setter 도 사용 가능합니다:**

```java
MailSendRequest mailSendRequest = new MailSendRequest();
...
mailSendRequest.setEmailTitle("이메일 제목");
mailSendRequest.setEmailConts("이메일 내용");
mailSendRequest.setRecvmnEmailAddr("plateer@plateer.com");
...
mailSender.send(mailSendRequest);
```

### sendBulk 추가

* 대량의 메시지를 한번에 전송하는 sendBulk 메서드가 추가되었습니다.
* `X2BEE-COMMON 0.6.3` 이후 사용 가능
  * `MailSendRequest` 객체를 List로 받아 이메일 전송을 처리합니다.
  * Process : 메일전송 목록 INSERT (동기), 메일전송 처리 (비동기)
  * 테이블.컬럼명 : ST\_EMAIL\_SND\_HIST.email\_trns\_stat\_cd \
    (`CM029`) – 전송 성공 : 30 → <mark style="color:$success;">발송완료</mark>, 전송 실패 : 20 → <mark style="color:$danger;">발송ERROR</mark>

1. **다른 프로젝트에서 이메일 전송 시 (api-common 호출)**

* **`x2bee-api-common-vanilla` 가 아닌 다른 프로젝트에서 이메일을 전송하는 경우 api-common API를 호출합니다.**
* 예제 소스와 같이 `MailSendRequest` 객체에 필요 파라미터들을 담아 POST 방식으로 `/api/common/interface/bizmessage/sendmail` End Point를 호출합니다.
* `commonApiUrl` 은 application.yml 에 명시된 URL 입니다.

2. **템플릿을 사용하는 경우 (필독)**

* 기존 각 프로젝트에 있었던 MailSender 를 Common으로 옮기면서 각 프로젝트에 있던 타임리프 템플릿 들도 함께 `x2bee-api-common-vanilla` 으로 이동했습니다.
* 따라서 템플릿을 새로 생성할 때에는 각 프로젝트 폴더에 맞는 경로에 넣어주셔야 합니다.

<figure><img src="/files/5YtQ2bG57TJABfVapV1A" alt="" width="375"><figcaption></figcaption></figure>

* **템플릿 경로 (`email/apibo`, `email/apidisplay` 등) 는 각 프로젝트의 `application.yml` 에 명시되어 template 변수를 Builder 와 Setter 에서 문자열 앞에 자동으로 추가합니다.**
* 따라서 템플릿을 명시할 때에는 `email/apibo` 이후의 경로부터 입력합니다.

  * ex) x2bee-api-display-vanilla 프로젝트 내 application.yml
  *

  ```
  <div align="left"><img src="https://tech.x2bee.com/download/attachments/108003385/image-20230719-051634.png?version=1&#x26;modificationDate=1689743799588&#x26;cacheVersion=1&#x26;api=v2" alt="" width="375"></div>
  ```

***

### sendTemplateMailBulk 추가

* 기존 mail api와 달리 템플릿 데이터를 상위 파라미터로 받아 메일을 보내는 sendTemplateMailBulk가 추가되었습니다.
* `X2BEE-COMMON 0.9.71` 이후 사용 가능
* 기존과 같이 템플릿 파일 경로 넣어서 사용하거나 템플릿 데이터를 문자열로 보내 사용할 수 있습니다.

### Sample 소스 — sendTemplateMailBulk

템플릿 파일 경로를 사용할 경우 예시:

```java
MailSendTemplateRequest mailSendTemplateRequest = new MailSendTemplateRequest();
mailSendTemplateRequest.setTemplate("email/apimember/Join_ko");
mailSendTemplateRequest.setMailList(mailSendRequestList);

restApiUtil.post(
  this.commonApiUrl + "/api/common/interface/bizmessage/sendTemplateMailBulk",
  mailSendTemplateRequest,
  new ParameterizedTypeReference<Response<Void>>() {}
).getPayload();
```

템플릿 문자열로 사용할 경우 예시:

```java
MailSendTemplateRequest mailSendTemplateRequest = new MailSendTemplateRequest();
mailSendTemplateRequest.setTemplate("<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8' /><title>회원가입완료 안내</title></head><body><tbody><td>[<span th:text='${userName}'></span>]님, 안녕하세요.<br />[<span th:text='${userName}'></span>]님의 회원가입을 진심으로 감사드립니다.</td></tbody></table></body></html>");
mailSendTemplateRequest.setMailList(mailSendRequestList);

restApiUtil.post(
  this.commonApiUrl + "/api/common/interface/bizmessage/sendTemplateMailBulk",
  mailSendTemplateRequest,
  new ParameterizedTypeReference<Response<Void>>() {}
).getPayload();
```

***


# 알림톡 발송

이 문서는 알림톡전송 소스와 방법에 대해 다룹니다.

첫 번째로는 Config 설정 방법 및 확인에 대해 설명합니다.\
두 번째로 각 업무단 작성 방법을 설명합니다.

{% hint style="info" %}
**\<update>**

2023.08.03

* failover 관련 가이드가 추가되었습니다.
* 알림톡 전송시 Api Common을 호출을 지향합니다.
  {% endhint %}

***

## Config 설정

**application.yml 예시:**

```yaml
spring:
  bizMessage:
    accessKey: VHIZ4hLLrhf6uyRIrk4F
    secretKey: pHvcggk52DXYOYYUDvXyLi5RkcmwneeMoOx8xoTd
    alimTalk:
      plusFriendId: "@X2BEE"
      serviceId: ncp:kkobizmsg:kr:3094856:x2bee
```

필수 속성

* <kbd>accessKey</kbd>
* <kbd>secretKey</kbd>
* <kbd>alimTalk.plusFriendId</kbd>
* <kbd>alimTalk.serviceId</kbd>

위 속성들은 NCP(Naver Cloud Platform) 에서 발급받아 작성합니다.

## Sample 소스 및 설명

* 프로젝트 예시: <https://gitlab.x2bee.com/x2bee-venus-beta/venus-x2bee-api-sample-vanilla>

**BizMessageController** 예시 (요약):

```java
... 
private final MessageSender messageSender;

/* 알림톡 샘플 */
@PostMapping("/alimTalk")
public ResponseEntity<Response> bizMessage() throws Exception {
    ...
    String templateCode = "PO2B";
    String template = "[#{coNm}] 배송시작 안내\n" +
                      "\n" +
                      "#{mbrNm} 고객님께서 주문하신 상품의 배송이 곧 시작됩니다.\n" +
                      "\n" +
                      "■ 주문상품 정보\n" +
                      "- 주문번호 : #{orderNo}\n" +
                      "- 상품명 : #{goodsNm}\n" +
                      "\n" +
                      "※ 고객센터 #{ccNo}(유료)"; // Template 문구 -> 변수명 처리로 변경됨.

    // SendParams params = 임시객체 (비즈니스 로직 VO 사용 권장)
    SendParams params = SendParams.builder()
        .coNm("X2BEE")
        .mbrNm("김플래")
        .orderNo("O0000000001")
        .goodsNm("양말 외 1건")
        .ccNo("02-3333-4444")
        .build();

    template = messageSender.replaceTemplateFormat(template, params);

    List<Buttons> buttons = new ArrayList<>();
    // 버튼 추가
    buttons.add(Buttons.builder()
        .linkPc("https://plateer.com")      // https:// 포함, 필수
        .linkMobile("https://plateer.com")  // https:// 포함, 필수
        .name("주문상세 보기")               // 템플릿 버튼 명과 정확히 일치해야 함
        .type("WL")                         // 템플릿 버튼 타입과 정확히 일치해야 함. 기본 WL
        .build());

    for (int i = 0; i < 1; i++) {
        messagesList.add(MessagesRequest.builder()
            .receiverPhoneNumber("01012341234")
            .content(template)
            .buttons(buttons)
            .build());
    }

    //======================================================
    // API-COMMON 에서 직접 호출시
    BizMessageResponse str = messageSender.sendAlimTalk(messagesList, templateCode);
    //======================================================

    // API-COMMON 호출시
    BizMessageRequest request = BizMessageRequest.builder()
        .messages(messagesList)     // 필수
        .templateCode(templateCode) // 필수
        .build();

    BizMessageResponse response = restApiUtil.post(
        this.commonApiUrl + "/api/common/interface/bizmessage/sendalimtalk",
        request,
        new ParameterizedTypeReference<Response<BizMessageResponse>>() {}
    ).getPayload();

    ...
}
...
```

* (1), (2), (7), (8) 은 아래 표를 참조하여 작성합니다.
* <mark style="color:red;">(2), (7), (8) 의 경우 Naver Cloud Platform 에서 승인받은 템플릿 내용과 버튼 명, 버튼 타입이 정확하게 일치해야 합니다.</mark>
* <kbd><mark style="color:red;">템플릿 내용 컬럼<mark style="color:red;"></kbd><mark style="color:red;">과 버튼 컬럼의</mark> <mark style="color:red;"></mark><kbd><mark style="color:red;">type<mark style="color:red;"></kbd><mark style="color:red;">과</mark> <mark style="color:red;"></mark><kbd><mark style="color:red;">name<mark style="color:red;"></kbd><mark style="color:red;">을 참조하십시오.</mark>

{% hint style="info" %}
템플릿 내용의 #{...} 변수명과 비즈니스 로직 VO의 변수명이 일치하면 replaceTemplateFormat에서 치환됩니다. 예제의 SendParams 는 제공하지 않습니다.
{% endhint %}

### NCP 승인 템플릿 목록 테이블

<table><thead><tr><th width="77.888916015625"></th><th width="107">TMPL-CODE (1)</th><th width="108.111083984375">구분</th><th>템플릿 내용 (2)</th><th>버튼 (7), (8)</th></tr></thead><tbody><tr><td>1</td><td>PO1A</td><td>상품Q&#x26;A 답변 완료</td><td><pre><code>[#{고객사명}] 상품Q&#x26;A 답변완료 안내 고객님께서 문의하신 내용에 대한 답변이 완료되었습니다. ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 상품Q&#x26;A 문의 답변 확인</td></tr><tr><td>2</td><td>PO2A</td><td>주문완료</td><td><pre><code>[#{고객사명}] 주문완료 안내 #{고객명} 고객님의 주문이 완료되었습니다. ■ 주문정보 - 주문번호 : #{주문번호} - 주문일 : #{주문일} - 상품명 : #{상품명외N건(수량)개} - 결제금액 : #{결제금액}원 - 배송지 : #{배송지} ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>3</td><td>PO2B</td><td>배송시작</td><td><pre><code>[#{고객사명}] 배송시작 안내 #{고객명} 고객님께서 주문하신 상품의 배송이 곧 시작됩니다. ■ 주문상품 정보 - 주문번호 : #{주문번호} - 상품명 : #{상품명외N건(수량)개} ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>4</td><td>PO2C</td><td>배송지연</td><td><pre><code>[#{고객사명}] 배송지연 안내 #{고객명} 고객님께서 주문하신 상품의 배송이 지연되고 있습니다. 불편을 드려 죄송합니다. 빠른시간 내 상품확보를 위해 최선을 다하겠습니다. 상품 수급이 불가하거나 장기화될 시 취소 안내를 드릴 수도 있습니다. ■ 주문상품 정보 - 주문번호 : #{주문번호} - 상품명 : #{상품명외N건(수량)개} 주문취소를 원하시는 경우, 고객센터로 연락 바랍니다. ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>5</td><td>PO2D</td><td>주문취소</td><td><pre><code>[#{고객사명}] 주문취소 안내 고객님의 주문(#{주문번호})이 취소되었습니다. ■ 주문 취소정보 - 주문번호 : #{주문번호} - 상품명 : #{상품명외N건(수량)개} - 환불금액 : #{환불금액} ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>6</td><td>PO2E</td><td>교환접수 (추가배송비 결제 요청)</td><td><pre><code>[#{고객사명}] 교환배송비 결제 안내 #{고객명} 고객님께서 신청하신 교환이 접수되었습니다. 마이페이지에서 추가 배송비를 결제하신 후에 교환이 시작됩니다. ■ 교환 주문정보 - 주문번호 : #{주문번호} - 상품명 : #{상품명외N건(수량)개} - 교환 옵션 : #{컬러,사이즈} → #{컬러,사이즈} - 추가 배송비: #{추가배송비} 교환 요청하신 상품은 고객사에 수거된 후, 새 상품이 출고됩니다. (상품 불량, 재고 부족 등의 이슈로 교환이 진행되지 않을 수 있는 점 양해 부탁드립니다.) 수거부터 교환 배송까지는 약 7일 정도 소요되며, 수거가 지연될 경우 고객사 고객센터로 문의 부탁드립니다. ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>7</td><td>PO2F</td><td>교환접수</td><td><pre><code>[#{고객사명}] 교환접수 안내 #{고객명} 고객님께서 신청하신 교환이 접수되었습니다. ■ 교환 주문정보 - 주문번호 : #{주문번호} - 상품명 : #{상품명외N건(수량)개} - 교환 옵션 : #{컬러,사이즈} → #{컬러,사이즈} 교환 요청하신 상품은 고객사에 수거된 후, 새 상품이 출고됩니다. (상품 불량, 재고 부족 등의 이슈로 교환이 진행되지 않을 수 있는 점 양해 부탁드립니다.) 수거부터 교환 배송까지는 약 7일 정도 소요되며, 수거가 지연될 경우 고객사 고객센터로 문의 부탁드립니다. ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>8</td><td>PO2G</td><td>교환 배송시작</td><td><pre><code>[#{고객사명}] 교환 배송시작 안내 #{고객명} 고객님께서 신청하신 교환 상품의 배송이 곧 시작됩니다. ■ 교환상품 정보 - 주문번호 : #{주문번호} - 상품명(수량) : #{상품명(수량)} ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>9</td><td>PO2H</td><td>반품접수</td><td><pre><code>[#{고객사명}] 반품접수 안내 #{고객명} 고객님께서 신청하신 반품이 접수되었습니다. ■ 반품 주문정보 - 주문번호 : #{주문번호} - 상품명 : #{상품명외N건(수량)개} 반품 수거는 평일 기준 2~3일 내에 배송 받으신 택배사 기사님을 통해 수거될 예정입니다. ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>10</td><td>PO2I</td><td>반품접수 (추가배송비 결제 요청)</td><td><pre><code>[#{고객사명}] 반품 배송비 결제 안내 #{고객명} 고객님께서 신청하신 반품이 접수되었습니다. 마이페이지에서 추가 배송비를 결제하신 후에 교환이 시작됩니다. ■ 반품 주문정보 - 주문번호 : #{주문번호} - 상품명 : #{상품명외N건(수량)개} - 추가 배송비: #{추가배송비} 반품 수거는 평일 기준 2~3일 내에 배송 받으신 택배사 기사님을 통해 수거될 예정입니다. ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>11</td><td>PO2J</td><td>반품완료</td><td><pre><code>[#{고객사명}] 반품완료 안내 #{고객명} 고객님께서 신청하신 반품이 완료되었습니다. 반품완료일을 기준으로 카드환불은 평일 35일 후 처리되며, 정확한 날짜는 카드사별로 상이합니다. 실시간 계좌이체의 경우, 평일 12일 후(오후 6시 전후) 환불됩니다. ■ 반품정보 - 주문번호 : #{주문번호} - 상품명 : #{상품명외N건(수량)개} - 환불금액 : #{환불금액} 배송비 등 차감으로 인해 환불금액이 상이할 수 있으며, 보다 자세한 내용은 환불내역을 확인 부탁드립니다. ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 주문상세 보기</td></tr><tr><td>12</td><td>PO3A</td><td>이벤트 당첨 - 경품</td><td><pre><code>[#{고객사명}] 이벤트 당첨 안내 #{고객명} 고객님, 이벤트 경품 당첨을 축하 드립니다. 당첨자 안내사항을 확인해 주세요. - 당첨된 이벤트: #{이벤트명} - 당첨 경품: #{당첨경품명} - 경품 발송일: #{경품발송일} 당첨된 경품은 이벤트 응모 시 입력한 주소지로 발송됩니다. #{경품발송일3일전날짜}까지 경품 수령지 주소를 확인/변경해 주세요. 경품 수령지 주소 변경: #{마이페이지참여내역} ※ 이 메시지는 고객님이 참여한 이벤트 당첨으로 지급된 경품 안내 메시지입니다. ※ 당첨된 경품의 제세공과금 22%는 당첨자 본인 부담입니다. ※ 고객센터 #{고객센터전화번호}(유료)
</code></pre></td><td>type : WL name : 마이페이지 참여내역</td></tr><tr><td>13</td><td>PO2K</td><td>1:1문의 답변 완료</td><td><pre><code>[#{고객사명}] 1:1 문의 답변완료 안내 #{고객명} 고객님, 문의하신 내용에 대한 답변이 완료되었습니다. 1:1문의에서 답변을 확인해주세요. ※ 고객센터 #{0000-0000}(유료)
</code></pre></td><td>type : WL name : 1:1문의 답변 확인</td></tr><tr><td>14</td><td>PO3B</td><td>휴면예정</td><td><pre><code>[#{고객사명}] 1년 이상 미로그인 회원 휴면 계정 전환 예정 안내 안녕하세요, #{고객명} 고객님 고객사명은 「개인정보보호법 제 39조 6」에 따라 최근 1년간 고객사명에 로그인하지 않으신 고객님의 개인정보를 보호하기 위해, 다음과 같이 고객님 계정 정보를 휴면 상태로 전환할 예정임을 안내드립니다. 휴면전환 계정 : #{ID} 휴면전환 예정 일자 : #{휴면전환예정일} 개인정보 보호 대상 : 회원 가입 시 입력한 개인정보와 주문 시 입력한 개인정보(이름, 성별, 생년월일, 전화번호, 이메일, 주소 등) ※ 고객사명의 다양한 혜택을 계속해서 이용하시고자 하는 경우, #{휴면전환예정일-1일}까지 고객사명에 로그인하시면 계속 이용이 가능합니다. ※ 고객센터 #{0000-0000}(유료)
</code></pre></td><td></td></tr><tr><td>15</td><td>PO3C</td><td>회원 가입</td><td><pre><code>[#{고객사명}] 회원가입완료 안내 #{고객명} 고객님, 회원 가입을 진심으로 감사드립니다. 앞으로도 많은 관심과 이용 부탁 드립니다. ※ 고객센터 #{0000-0000}(유료)
</code></pre></td><td></td></tr><tr><td>16</td><td>PO3D</td><td>마케팅 수신동의</td><td><pre><code>[#{고객사명}] 정기적 수신동의 안내 #{고객명} 고객님, 이 메시지는 정보통신망법에 따라 2년마다 발송되는 광고성 정보 수신동의 확인 메시지 입니다. 이전 수신동의일자 : #{YYYY}년 #{MM}월 #{DD}일 앞으로도 유익한 소식과 다양한 혜택으로 찾아뵙겠습니다.
</code></pre></td><td></td></tr><tr><td>17</td><td>PO3E</td><td>회원 탈퇴</td><td><pre><code>[#{고객사명}] 개인정보 파기 및 탈퇴 예정 안내 #{고객명} 고객님, 고객사명은 고객님의 탈퇴 요청에 따라 고객님의 개인정보를 보호하기 위해, 다음과 같이 개인정보 파기 및 탈퇴 처리가 진행될 예정입니다. 탈퇴 계정 : #{ID} 파기 대상 개인정보 : 이름, 성별, 생년월일, 전화번호, 이메일, 주소 등 ※ 개인정보 파기 및 탈퇴 이후에 서비스 이용을 원하시면, 신규 회원가입을 하셔야 합니다. ※ 본 메시지는 고객사명 온라인 쇼핑몰의 개인정보처리방침에 따라 발송된 메시지입니다. ※ 고객센터 #{0000-0000}(유료)
</code></pre></td><td></td></tr><tr><td>18</td><td>PO3F</td><td>임시 비밀번호 안내</td><td><pre><code>[#{고객사명}] 임시 비밀번호 안내 #{고객명}고객님의 임시 비밀번호는 [#{임시비밀번호}]입니다.
</code></pre></td><td></td></tr><tr><td>19</td><td>PO3G</td><td>관리자 인증번호 안내</td><td><pre><code>[#{고객사명}] 임시 비밀번호 안내 #{사용자명}님께서 요청하신 임시 비밀번호는 [#{인증번호}] 입니다.
</code></pre></td><td></td></tr></tbody></table>

(3) 템플릿 내용의 #{...} 과 교체할 파라미터를 비즈니스 로직에서 사용중인 VO 변수에 해당하는 변수로 set 합니다.

* <mark style="color:red;">템플릿 변수(#{…}) 명과 VO의 변수명이 일치하면 replace 됩니다.</mark>
* <mark style="color:red;">예제의 SendParams 는 제공되지 않습니다.</mark>

(4) 템플릿 내용과 파라미터 리스트를 <kbd>MessageSender</kbd>의 `replaceTemplateFormat` 메서드를 사용하여 매핑시킵니다.

(5), (6) 링크는 두개 모두 필수이며 **https\://** 를 반드시 포함해야 합니다.

(9) `x2bee-api-common-vanilla` 가 아닌 다른 프로젝트에서 알림톡을 전송하는 경우 api-common API를 호출합니다.\
예제 소스와 같이 <kbd>BizMessageRequest</kbd> 객체에 필요 파라미터들을 담아 POST 방식으로 <kbd>/api/common/interface/bizmessage/sendalimtalk</kbd> End Point를 호출합니다.

* <kbd>messages</kbd> 와 <kbd>templateCode</kbd> 는 필수 파라미터입니다.
* <kbd>commonApiUrl</kbd> 은 application.yml 에 명시된 URL 입니다.

## Failover 설정(알림톡 발송 실패시)

* 현재 X2BEE 카카오톡 채널은 Failover 설정이 <mark style="color:red;">True</mark>로 되어있어 <kbd>messages.useSmsFailover</kbd> <mark style="color:red;">변수 작성여부에 관계없이 true로 default 요청</mark>됩니다. \
  따라서 Failover 를 사용하지 않는다면 <kbd>messages.useSmsFailover</kbd> 변수를 false로 설정해야 합니다.
* <kbd>failoverConfig</kbd> 내 파라미터는 모두 Optional 로 작성하지 않으면 아래 각 파라미터 비고란에 적힌 내용과 같이 발송됩니다.
* [<kbd>messages.failoverConfig.from</kbd>](#user-content-fn-1)[^1] 의 경우 NCP Console 에 지정된 발신번호로 자동등록되오니 작성할 필요가 없습니다.

<table><thead><tr><th width="172.5555419921875">항목</th><th width="120.1112060546875">Mandatory</th><th width="88.2222900390625">Type</th><th>설명</th><th>비고</th></tr></thead><tbody><tr><td>plusFriendId</td><td>Mandatory</td><td>String</td><td>카카오톡 채널명 ((구)플러스친구 아이디)</td><td></td></tr><tr><td>templateCode</td><td>Mandatory</td><td>String</td><td>템플릿 코드</td><td></td></tr><tr><td>messages</td><td>Mandatory</td><td>Object</td><td>메시지 정보</td><td>- 아래 항목 참조 (messages.XXX)<br>- 최대 100개</td></tr><tr><td>messages.useSmsFailover</td><td>Optional</td><td>Boolean</td><td>SMS Failover 사용 여부</td><td>- Failover가 설정된 카카오톡 채널에서만 사용 가능<br>- 기본: 카카오톡 채널의 Failover 설정 여부를 따름</td></tr><tr><td>messages.failoverConfig</td><td>Optional</td><td>Object</td><td>Failover 설정</td><td>아래 항목 참조</td></tr><tr><td>messages.failoverConfig.type</td><td>Optional</td><td>String</td><td>Failover SMS 메시지 Type</td><td>- SMS 또는 LMS<br>- 기본: content 길이에 따라 자동 적용 (90 bytes 이하 SMS, 초과 LMS)</td></tr><tr><td>messages.failoverConfig.from</td><td>Optional</td><td>String</td><td>Failover SMS 발신번호</td><td>- 기본: Failover 설정 시 선택한 발신번호<br>- 승인되지 않은 발신번호 사용시 Failover 동작 안함</td></tr><tr><td>messages.failoverConfig.subject</td><td>Optional</td><td>String</td><td>Failover SMS 제목</td><td>- LMS type으로 동작할 때 사용<br>- 기본: 카카오톡 채널명</td></tr><tr><td>messages.failoverConfig.content</td><td>Optional</td><td>String</td><td>Failover SMS 내용</td><td>기본: 알림톡 메시지 내용 (버튼 제외)</td></tr></tbody></table>

***

[^1]:


# 검색 및 기타 환경 설정

## 개요

검색 엔진 사용 방법에 대한 설명과 함께 기타 환경 설정 중 Swagger3.x를 사용하여 API 문서를 자동화하고 테스트할 수 있는 방법에 대해 안내합니다.

***

## 문서 구성

<details>

<summary><a href="/pages/62fd7cd27411c7a11804cc796f7e1c3bcefefa33"><strong>검색 및 인덱싱</strong> </a></summary>

**검색 데이터 추가/조회 방법에 대해서 설명합니다.**

</details>

<details>

<summary><a href="/pages/a25010a9c274f83498a71994bfc705f7fa40cfa8"><strong>Swagger3.x</strong> </a></summary>

설정 파일, API 문서 작성 방법, API 테스트 방법을 설명합니다.

</details>




---

[Next Page](/llms-full.txt/1)

