> For the complete documentation index, see [llms.txt](https://tech.x2bee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tech.x2bee.com/dev-guide/pjt-prepare/publish-your-docs/api.md).

# 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에서 확인할 수 있습니다.

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tech.x2bee.com/dev-guide/pjt-prepare/publish-your-docs/api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
