Skip to content

bareunio/project-base

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

project-base

바른아이오 공통 프레임워크

목차

버전 정보

[자바] java : 1.8
[스프링 부트] org.springframework.boot : 2.7.12
[전자정부 프레임워크] org.egovframework : 4.2 

의존성

[build.gradle]

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
	implementation 'org.springframework.boot:spring-boot-starter-security'

    implementation("org.egovframe.rte:org.egovframe.rte.bat.core:4.2.0") {
		exclude group: 'org.egovframe.rte', module: 'org.egovframe.rte.fdl.logging'
	}

	implementation 'org.apache.commons:commons-collections4:4.4'
	implementation 'org.apache.commons:commons-text:1.10.0'
	implementation 'org.apache.poi:poi-ooxml:5.2.4'

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	testImplementation 'io.projectreactor:reactor-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

라이브러리 생성

Nexus를 고려하고 있지만 현재는 jar로 제공

[build.gradle]

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.7.12'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
	id 'maven-publish'
	id 'com.github.johnrengelman.shadow' version '7.1.2'
}

...(중략)

shadowJar {
    archiveFileName.set('project-base-0.0.1-all.jar')
}

publishing {
	publications {
		mavenJava(MavenPublication) {
			artifact shadowJar
		}
	}
}

com.github.johnrengelman.shadow : jar 파일 생성 시 전체 의존성(dependencies) 포함

[jar 파일 생성 방법]

./gradlew shadowJar 

위의 명령어 실행 시, build/libs 경로에 project-base-0.0.1-all.jar 생성된다.

프레임워크 모듈

자세한 예제 코드는 project-sample 에서 설명한다.

API

io.bareun.base.api

외부 API를 호출하기 위한 모듈 (동기, 비동기 지원)

/**
 * {@link WebClient}를 사용하여 웹 API 호출을 수행하는 클라이언트입니다.
 */
@Slf4j
@Component
public class WebApiClient {

    private final WebClient webClient;
    
    ...(중략)

    /**
     * 주어진 API 요청을 동기적으로 호출하고 응답을 반환합니다.
     *
     * @param <T> 응답 본문의 타입
     * @param request 호출할 API 요청
     * @return API 요청의 응답 본문
     */
    public <T> T callReturn(ApiRequest<T> request) {
        return retrieve(request).block();
    }

    /**
     * 주어진 API 요청을 비동기적으로 호출합니다.
     *
     * @param <T> 응답 본문의 타입
     * @param request 호출할 API 요청
     */
    public <T> void call(ApiRequest<T> request) {
        retrieve(request).subscribe(request::subscribe, request::error);
    }
    
    ...(중략)
}

WebClient를 사용하여 API를 호출하며 callReturn(ApiRequest<T> request)call(ApiRequest<T> request) 는 각각 동기, 비동기를 호출하는 메서드이다.

/**
 * API 요청을 표현하는 인터페이스입니다.
 *
 * @param <T> 응답 타입
 */
public interface ApiRequest<T> {

    /**
     * HTTP 메서드를 반환합니다.
     *
     * @return 요청에 사용될 HTTP 메서드
     */
    HttpMethod getMethod();

    /**
     * 요청할 URL을 반환합니다.
     *
     * @return 요청할 URL
     */
    String getUrl();

    /**
     * 요청 본문을 반환합니다.
     *
     * @return 요청 본문
     */
    Object getBody();

    /**
     * 응답 타입을 반환합니다.
     *
     * @return 응답 타입 클래스
     */
    Class<T> getResponseType();
    
    ...(중략)
}

API 요청 인터페이스, 인터페이스 구현한 클래스로 WebApiClient의 메소드를 호출한다.

추가로, Content-Type: application/json를 지원하는 JsonApiRequest 인터페이스와 편리하게 인스턴스를 생성하는 빌더 클래스 ApiRequestBuilder도 제공한다.

공통

io.bareun.base.common

공통으로 사용하는 Map 클래스와 공통 Controller 응답 클래스, 자주 사용하는 유틸 클래스

/**
 * 키-값 쌍을 보관하고 조작하는 데 사용되는 DTO 맵 클래스입니다.
 * 기본적으로 키를 camelCase로 변환합니다.
 */
public class BaseMap extends HashMap<String, Object> {
    ...(중략)
}

공통으로 사용하는 Map 클래스로, 키는 camelCase로 변환을 시킵니다. 편하게 사용 한 org.apache.commons.collections4.MapUtils에 의거한 메소드도 추가하였습니다.

/**
 * BaseMap 클래스에 대한 테스트 클래스입니다.
 */
class BaseMapTest {

    /**
     * JSON 문자열 파싱을 테스트하는 메서드입니다.
     * JSON 문자열을 BaseMap 객체로 변환하고,
     * 변환된 BaseMap 객체의 값을 확인합니다.
     */
    @Test
    void jsonParsing() {
        String json = "{\n" +
                "  \"search_map\": {\n" +
                "    \"string_value\": \"formal_contents\",\n" +
                "    \"long_value\": 1203405603,\n" +
                "    \"double_value\": 1.2,\n" +
                "    \"integer_value\": 10,\n" +
                "    \"map_value\" : {\n" +
                "      \"hello_world\" : \"I'm project base\"\n" +
                "    },\n" +
                "    \"list_string_value\": [\n" +
                "      \"one\",\n" +
                "      \"two\"\n" +
                "    ],\n" +
                "    \"list_map_value\": [\n" +
                "      {\n" +
                "        \"key\": \"value\"\n" +
                "      }\n" +
                "    ]\n" +
                "  }\n" +
                "}";

        BaseMap baseMap = BaseMap.of(json);

        System.out.println("baseMap = " + baseMap);

        assertThat(baseMap).isNotNull();
        assertThat(baseMap.getMap("searchMap")).isNotNull();
        assertThat(baseMap.getMap("searchMap").getString("stringValue")).isEqualTo("formal_contents");
        assertThat(baseMap.getMap("searchMap").getLong("longValue")).isEqualTo(1203405603);
        assertThat(baseMap.getMap("searchMap").getDouble("doubleValue")).isEqualTo(1.2);
        assertThat(baseMap.getMap("searchMap").getInteger("integerValue")).isEqualTo(10);
        assertThat(baseMap.getMapByKeys("searchMap", "mapValue").getString("helloWorld")).isEqualTo("I'm project base");
        assertThat(baseMap.getMap("searchMap").getList("listStringValue").get(0)).isEqualTo("one");

        List<BaseMap> list = baseMap.getMap("searchMap").getMapList("listMapValue");
        assertThat(list.size()).isEqualTo(1);

        BaseMap item = list.get(0);
        assertThat(item.getString("key")).isEqualTo("value");
    }
}

json 문자열을 BaseMap으로 변환하고 Key값을 이용하여 Value값을 찾는 테스트 코드 입니다. 사용 예제로 참고

/**
 * API 응답을 위한 클래스입니다.
 * <p>
 * 이 클래스는 API 요청에 대해 반환할 데이터 규격을 정의합니다.
 */
@Data
@Builder
@JsonInclude(NON_NULL)
public class ApiResponse<T> {

    /**
     * 응답 코드
     */
    private final int code;

    /**
     * 응답 메시지
     */
    private final String message;

    /**
     * API 응답의 결과 데이터를 포함
     */
    private final T result;

    /**
     * 실패 응답을 생성하는 메소드.
     *
     * @param code    응답 코드
     * @param message 응답 메시지
     */
    public static ApiResponse<?> fail(int code, String message) {
        return ApiResponse.builder()
                .code(code)
                .message(message)
                .build();
    }

    /**
     * 성공 응답을 생성하는 메소드.
     *
     * @param result 결과 데이터
     */
    public static <T> ApiResponse<T> success(T result) {
        return ApiResponse.<T>builder()
                .code(HttpStatus.OK.value())
                .message(HttpStatus.OK.getReasonPhrase())
                .result(result)
                .build();
    }

    /**
     * 결과 데이터 없는 성공 응답을 생성하는 메소드.
     */
    public static <T> ApiResponse<T> success() {
        return ApiResponse.<T>builder()
                .code(HttpStatus.OK.value())
                .message(HttpStatus.OK.getReasonPhrase())
                .build();
    }
}

다음과 같은 json 결과 값을 공통 처리 한다. 정상 응답시 success 메소드를 이용하여 리턴하면 된다.

{
  "code" : (int),
  "message" : (String),
  "result" : (T)
}

유틸 클래스

  • ObjectMapperUtils : JSON 객체를 변환하는 유틸 클래스 (String-T / Object-T)
  • RequestUtils : HttpServletRequestHttpSession 처리하는 유틸 클래스
  • ResponseUtils : HttpServletResponse 유틸 클래스
  • SecurityUtils : Spring Security 유틸 클래스

예외 처리

io.bareun.base.exception

기본적으로 예외 처리에 사용하는 에러 코드 및 Exception정의, 예외 핸들러 클래스가 제공

public interface ErrorCode {

    int getCode();

    String getMessage();
}

기본 에러 코드 인터페이스이다. 각 프로젝트에서 ErrorCode를 구현해서 사용하면 된다. 기본으로 ErrorCode를 구현한 에러코드는 다음과 같다.

/**
 * ErrorCode는 예외 처리에 사용되는 오류 코드를 정의하는 열거형 클래스입니다.
 * 각 열거 상수는 오류 코드와 메시지를 가지고 있습니다.
 */
@Getter
@RequiredArgsConstructor
public enum BaseErrorCode implements ErrorCode {

    /**
     * HTTP 40x 코드 기반 5자리
     */
    BAD_REQUEST(40000, "잘못된 요청 값 입니다."),
    REQUIRED(40001, "%s 값은 필수입니다."),
    VALIDATE(40002, "%s 값이 올바르지 않습니다."),

    /**
     * HTTP 50x 코드 기반 5자리
     */
    UNKNOWN(50000, "알 수 없는 에러입니다."),
    ;

    private final int code;
    private final String message;
}

앞의 3자리는 HTTP Status Code 코드로 구성하며, 뒤의 2자리는 커스텀 코드이다.

/**
 * BusinessException은 비즈니스 로직에서 발생할 수 있는 예외를 나타내는 클래스입니다.
 * RuntimeException을 상속받아 unchecked 예외로 정의되어 있습니다.
 */
@Getter
public class BusinessException extends RuntimeException {

    private final ErrorCode errorCode;

    /**
     * 주어진 메시지를 가지고 기본적인 UNKNOWN 에러 코드로 BusinessException을 생성합니다.
     *
     * @param message 예외 메시지
     */
    public BusinessException(String message) {
        super(message);
        this.errorCode = BaseErrorCode.UNKNOWN;
    }

    /**
     * 주어진 에러 코드로 BusinessException을 생성합니다.
     *
     * @param errorCode 에러 코드
     */
    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }

    /**
     * 주어진 에러 코드와 추가적인 인자를 사용하여 BusinessException을 생성합니다.
     * 에러 메시지는 포맷팅된 형태로 제공됩니다.
     *
     * @param errorCode 에러 코드
     * @param args      포맷팅에 사용될 인자들
     */
    public BusinessException(ErrorCode errorCode, Object... args) {
        super(String.format(errorCode.getMessage(), args));
        this.errorCode = errorCode;
    }
}

위의 ErrorCode를 기반으로 하는 예외 클래스이다. 해당 예외 클래스는 업무관련된 사용자 에러메세지를 처리한다.

/**
 * ApiExceptionHandler는 Spring Web MVC에서 발생하는 예외를 처리하는 클래스입니다.
 * RestControllerAdvice 애노테이션을 사용하여 모든 @RestController에서 발생하는 예외를 처리합니다.
 * 각 예외에 따라 적절한 HTTP 상태 코드와 메시지를 반환합니다.
 */
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class ApiExceptionHandler {

    /**
     * BusinessException을 처리하는 메서드입니다.
     *
     * @param e 발생한 BusinessException 객체
     * @return ApiResponse 객체 (실패 응답)
     */
    @ExceptionHandler(BusinessException.class)
    public ApiResponse<?> buisnessException(BusinessException e) {
        log.error("ApiException buisnessException", e);
        return ApiResponse.fail(e.getErrorCode().getCode(), e.getMessage());
    }
    
    ...(중략)
}

위의 예외 클래스를 핸들러하는 클래스이다. ApiResponsefail() 메소드를 반환한다.

파일 처리

io.bareun.base.file

첨부 파일 및 엑셀 파일 업로드, 다운로드 지원하는 모듈

/**
 * FileManager 인터페이스는 파일 관리 기능을 정의하는 인터페이스입니다.
 * 구현 클래스에서 파일 업로드, 다운로드, 파일명 생성 등의 기능을 제공합니다.
 */
public interface FileManager {

    /**
     * 파일이 저장될 디렉토리 경로를 반환합니다.
     *
     * @return 파일이 저장될 디렉토리 경로
     */
    String getDirectory();

    ...(중략)

    /**
     * 주어진 MultipartFile을 업로드하고 AttachUploadFile 객체로 반환합니다.
     *
     * @param file 업로드할 MultipartFile 객체
     * @return AttachUploadFile 객체
     */
    default AttachUploadFile upload(MultipartFile file) {
        validate(file);

        String originalFileName = file.getOriginalFilename();
        String storedFileName = createStoredFileName(originalFileName);

        FileUtils.upload(file, getFullPath(storedFileName));

        return AttachUploadFile.of(originalFileName, storedFileName);
    }

    /**
     * DownloadFile 객체를 사용하여 파일을 다운로드합니다.
     *
     * @param downloadFile 다운로드할 파일 정보를 포함한 DownloadFile 객체
     * @param <T>          응답 바디 타입
     * @return ResponseEntity 객체로 감싼 다운로드 결과
     */
    default <T> ResponseEntity<T> download(DownloadFile<T> downloadFile) {
        return ResponseEntity.ok().headers(downloadFile.getHeaders()).body(downloadFile.getBody());
    }
}

파일을 관리하는 인터페이스로, 저장 디렉토리를 가져오는 getDirectory()를 필수로 구현해야한다. 업로드와 다운로드 기능을 기본 제공한다.

/**
 * UploadFile 인터페이스는 업로드된 파일의 원본 파일명과 저장된 파일명을 제공하는 메서드를 정의합니다.
 */
public interface UploadFile {

    /**
     * 업로드된 파일의 원본 파일명을 반환합니다.
     *
     * @return 원본 파일명
     */
    String getOriginalFileName();

    /**
     * 업로드된 파일의 저장된 파일명을 반환합니다.
     *
     * @return 저장된 파일명
     */
    String getStoredFileName();
}

업로드 파일 인터페이스로, 인터페이스 구현체로 AttachUploadFileExcelUploadFile<T>는 각각 첨부파일, 엑셀파일 업로드 클래스이다. 업로드한 정보로 DB에서 관리될 수 있다.

/**
 * DownloadFile 인터페이스는 파일 다운로드 정보를 정의하는 인터페이스입니다.
 * 구현 클래스에서는 다운로드할 파일명, HTTP 헤더, 응답 바디를 제공해야 합니다.
 *
 * @param <T> 다운로드할 데이터의 타입
 */
public interface DownloadFile<T> {

    /**
     * 다운로드할 파일명을 반환합니다.
     *
     * @return 다운로드할 파일명
     */
    String getDownloadFileName();

    /**
     * 다운로드할 파일에 대한 HTTP 헤더를 반환합니다.
     *
     * @return HTTP 헤더 객체
     */
    HttpHeaders getHeaders();

    /**
     * 다운로드할 데이터의 본문을 반환합니다.
     *
     * @return 다운로드할 데이터의 본문
     */
    T getBody();
}

다운로드 파일 인터페이스로, 인터페이스 구현체로 AttachDownloadFileExcelDownloadFile는 각각 첨부파일, 엑셀파일 다운로드 클래스이다.

구현 클래스를 인스턴스하여 FileManagerdownload 메소드를 호출하면 응답 값으로 파일 다운로드가 실행된다.

/**
 * ExcelWriter 인터페이스는 Excel 파일 작성을 위한 기능을 정의합니다.
 * <p>
 * 구현체는 Excel 파일의 헤더 스타일, 헤더 이름 및 값을 작성하기 위한 메서드를 포함합니다.
 * </p>
 *
 * @param <T> Excel 파일에 작성할 객체의 타입
 */
public interface ExcelWriter<T> {

    /**
     * Excel 파일에 작성할 데이터 리스트를 반환합니다.
     *
     * @return Excel 파일에 작성할 데이터 리스트
     */
    List<?> getList();

    /**
     * Excel 파일에 작성할 객체의 클래스 타입을 반환합니다.
     *
     * @return Excel 파일에 작성할 객체의 클래스 타입
     */
    Class<T> getType();
    
    ...(중략)
}

엑셀 다운로드 시, List<?>의 데이터를 엑셀로 쓰는 인터페이스이다. 기본 구현 클래스로 DefaultExcelWriter제공

파일 유틸 클래스

  • FileUtils : 기본 첨부 파일 유틸 클래스
  • ExcelFileUtils : 엑셀 파일 관련 유틸 클레스

로깅

io.bareun.base.log

@RestController에 대한 HTTP 로깅을 공통으로 제공한다.

/**
 * ApiLoggingAspect는 REST 컨트롤러의 HTTP 요청을 로깅하기 위한 Aspect입니다.
 * <p>
 * 이 클래스는 @RestController 어노테이션이 붙은 클래스 내에서 메서드 호출 전에 HTTP 요청을 로깅합니다.
 * 로깅할 정보로는 IP 주소, HTTP 메서드, 요청 URL, 쿼리 스트링, 요청 바디가 포함됩니다.
 */
@Slf4j
@Aspect
@Component
public class ApiLoggingAspect {

    /**
     * {@link org.springframework.web.bind.annotation.RestController} 어노테이션이 붙은 클래스
     * 내의 모든 메서드를 포인트컷으로 설정합니다.
     */
    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    public void restController() {
    }

    /**
     * 포인트컷에서 지정한 메서드 호출 전에 HTTP 요청을 로깅하는 메서드입니다.
     *
     * @param joinPoint 조인 포인트 객체로, 호출된 메서드와 그 파라미터 등을 추출하는 데 사용됩니다.
     */
    @Before("restController()")
    public void httpLogging(JoinPoint joinPoint) {
        String remoteAddr = getRemoteAddr();
        String method = getMethod();
        String requestURL = getRequestURL();
        String queryString = getQueryString();

        String requestBody = getRequestBody(joinPoint);

        log.info("HTTP Logging IP : {} | Method : {} | URL : {} | Query : {} | Body {}",
                remoteAddr, method, requestURL, queryString, requestBody);
    }
    
    ...(중략)
}

AOP를 이용하여 HTTP의 정보를 로깅한다.

[로그 포맷]

HTTP Logging IP : {} | Method : {} | URL : {} | Query : {} | Body {}

샘플 프로젝트

https://github.com/bareunio/project-sample

이슈

이슈 및 개선사항은 아래의 링크를 통해 등록 해주세요

https://github.com/bareunio/project-base/issues