Spring Framework

[Spring Boot] REST-API 공통 Response 값 포맷 개발 과정

「김동윤」 2023. 3. 15. 13:59

CRUD를 구현할때 R(Read)을 제외한 API들의 Response 값을 공통으로 포맷하여 프론트에 리턴해주는 기능을 개발했던 과정을 정리해 보겠습니다.

 

1. Response 값을 공통으로 포맷하여 리턴해줄 때 얻게 되는 장점

 

- GET 요청 API를 제외한 모든 API들의 Response 값이 동일하기 때문에 백엔드와 프론트 둘 다 구조적인 측면에서 좋습니다.

- 프론트 입장에서 공통 Response 값을 보고 편리하게 응답 상태를 구분할 수 있습니다.

 

 

2. Response 값 CASE 정리

 

CASE 1. 정상 (status = success)

ex)

 

 

CASE 2. 실패 (status = fail)

ex)

 

 

CASE 3. 에러 (status = error) 

 

 

 

 

3. 공통 Response값에 사용될 ApiResult Class 및 공통 에러 데이터를 담을 ApiExceptionEntity Class 생성

 

3-1. ApiResult.java  

package com.example.login.common.api;

import com.example.login.common.error.ApiExceptionEntity;
import lombok.*;

@Getter
@NoArgsConstructor
@ToString
public class ApiResult{
    private String status;
    private String message;
    private ApiExceptionEntity exception;

    @Builder
    public ApiResult(String status, String message, ApiExceptionEntity exception) {
        this.status = status;
        this.message = message;
        this.exception = exception;
    }
}

 

3-2. ApiExceptionEntity.java

package com.example.login.common.error;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.springframework.http.HttpStatus;

@Getter
@ToString
public class ApiExceptionEntity {
    private String errorCode;
    private String errorMessage;

    @Builder
    public ApiExceptionEntity(HttpStatus status, String errorCode, String errorMessage){
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }
}

 

 

 

4. CASE 1 & CASE 2 구현

(※ 에러 처리(CASE 3) 구현은 다음 글에서 정리해 보도록 하겠습니다. )

 

 

4-1. UserController.java

@ApiOperation(value="로그인", notes="Status Fail Case"
			+ "\n 1. 아이디가 틀렸을 때 -> message: idFail"
			+ "\n 2. 비밀번호가 틀렸을 때 -> message: pwFail")
	@PostMapping("/login")
	public ApiResult login(@RequestBody LoginRequestDto loginRequestDto, HttpServletRequest request, HttpServletResponse response) {

		return service.userLoginCheck(loginRequestDto, service.selectUser(loginRequestDto.getId()), response, request);
	}

 

 

4-2. UserService.java

@Override
public ApiResult userLoginCheck(LoginRequestDto loginRequestDto, UserVO userVo, HttpServletResponse response, HttpServletRequest request) {

		if(userVo != null) {

			BCryptPasswordEncoder encode = new BCryptPasswordEncoder();

			if(encode.matches(loginRequestDto.getPassword(),userVo.getPassword())) {
				request.getSession().setAttribute("login",userVo);

				//자동로그인을 체크했을시에 실행
				if(loginRequestDto.isAutoLogin()) {

					//3개월뒤의 초
					long second = 60 * 60 * 24 * 90;

					//쿠키생성
					Cookie cookie = new Cookie("loginCookie", request.getSession().getId());
					cookie.setPath("/");
					cookie.setMaxAge((int)second);
					response.addCookie(cookie);

					//3개월뒤의 밀리초를 날짜로 변환
					long millis = System.currentTimeMillis() + (second * 1000);
					Date limitDate = new Date(millis);

					//DB에 세션아이디,쿠키만료날짜,회원 아이디 전달
					userAutoLogin(request.getSession().getId(), limitDate, loginRequestDto.getId());
				}

				apiResult = ApiResult.builder()
						.status("success")
						.message("loginSuccess")
						.build();

			}else {
				apiResult = ApiResult.builder()
						.status("fail")
						.message("pwFail")
						.build();
			}
		}else {
			apiResult = ApiResult.builder()
					.status("fail")
					.message("idFail")
					.build();
		}

		return apiResult;

	}

 

 

 

5. Test 코드 작성

 

UserControllerTest.java

@Test
void testLogin() {
        try {
            UserVO userVo = new UserVO();
            userVo.setId("testId");
            userVo.setPassword("testPw");

            String jsonData = new Gson().toJson(userVo);

            mvc.perform(post("/api/user/login")
               .contentType(MediaType.APPLICATION_JSON)
               .content(jsonData))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.status", "success").exists());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }