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

***
