https://computerreport.tistory.com/88
[일정관리JPA] API 명세 및 ERD작성
이전에는 JDBC 를 활용하여 따로 코드를 작성했지만 JPA 를 사용하면 더 간편하게 데이터베이스를 생성 가능하기에 이번 프로젝트에 활용해보았다. 개발 일정2025.03.26 ~ 2025.04.04프로젝트명일정 관
computerreport.tistory.com
API 와 ERD 작성을 완료하고 디렉토리 및 파일 분리를 하였다.
todoProject
├── src/
│ ├── TodoJpaApplication.java # 애플리케이션의 진입점
│ │
│ ├── common # 공통 상수 및 유틸리티 클래스
│ │ └── Const.java
│ │
│ ├── config # 설정 관련 클래스
│ │ ├── PasswordEncoder.java # 비밀번호 인코딩 관련 설정
│ │ └── WebConfig.java # 웹 관련 설정
│ │
│ ├── controller # REST API 엔드포인트
│ │ ├── LoginController.java # 로그인 관련 API
│ │ ├── TodoController.java # Todo 관련 API
│ │ └── UserController.java # 사용자 관련 API
│ │
│ ├── dto # 데이터 전송 객체 (DTO)
│ │ ├── ErrorResponseDto.java # 오류 응답 DTO
│ │ ├── LoginRequestDto.java # 로그인 요청 DTO
│ │ ├── LoginResponseDto.java # 로그인 응답 DTO
│ │ ├── TodoRequestDto.java # Todo 요청 DTO
│ │ ├── TodoResponseDto.java # Todo 응답 DTO
│ │ ├── UserRequestDto.java # 사용자 요청 DTO
│ │ └── UserResponseDto.java # 사용자 응답 DTO
│ │
│ ├── entity # 엔티티 클래스
│ │ ├── BaseEntity.java # 기본 엔티티 클래스
│ │ ├── Todo.java # Todo 엔티티 클래스
│ │ └── User.java # 사용자 엔티티 클래스
│ │
│ ├── Exception # 사용자 정의 예외 클래스
│ │ ├── PasswordEqualsCheckException.java # 비밀번호 불일치 예외
│ │ └── TodoNotFindException.java # Todo 항목 미발견 예외
│ │
│ ├── filter # 필터 클래스
│ │ └── LoginFilter.java # 로그인 필터 클래스
│ │
│ ├── handler # 예외 처리 클래스
│ │ └── AllExceptionHandler.java # 모든 예외를 처리하는 핸들러
│ │
│ ├── repository # 데이터베이스 작업을 처리하는 리포지토리
│ │ ├── TodoRepository.java # Todo 리포지토리 인터페이스
│ │ └── UserRepository.java # 사용자 리포지토리 인터페이스
│ │
│ └── service # 비즈니스 로직을 처리하는 서비스
│ ├── LoginService.java # 로그인 서비스
│ ├── TodoService.java # Todo 서비스
│ └── UserService.java # 사용자 서비스
│
└── resources
├── application.properties # 애플리케이션 설정 파일
├── static # 정적 자원 (CSS, JS, 이미지 등)
└── templates # 템플릿 파일 (HTML 등)
test
└── java
└── TodoJpaApplicationTests.java # 애플리케이션 테스트 클래스
일정,유저의 CPUD 는 이전 프로젝트에서 다뤄봤기 때문에 이번엔 로그인 기능과 예외처리 기능을 중점으로 작성해볼려고 한다.
✅ 로그인
- Cookie / Session 을 활용
- 필터를 활용하여 인증 처리(회원가입,로그인 요청은 인증제외)
- 이메일과 비밀번호를 활용한 기능 구현
♐ LoginFilter
더보기
public class LoginFilter implements Filter {
private static final String[] WHITE_LIST = {"/users/signup", "/logout", "/session-login", "/check"};
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 화이트리스트에 없는 요청에 대해 로그인 체크
if (!isWhiteList(requestURI)) {
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute(Const.LOGIN_USER) == null) {
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType("application/json");
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.getWriter().write("message : 로그인 해주세요");
return;
}
}
chain.doFilter(request, response);
}
/**
* 요청 URI가 화이트리스트에 포함되어 있는지 확인합니다.
*
* @param requestURI 요청 URI
* @return 화이트리스트에 포함되어 있으면 true, 그렇지 않으면 false
*/
private boolean isWhiteList(String requestURI) {
return PatternMatchUtils.simpleMatch(WHITE_LIST, requestURI);
}
}
♐ WebConfig
더보기
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 로그인 필터를 등록하는 빈을 생성합니다.
*
* @return 필터 등록 빈
*/
@Bean
public FilterRegistrationBean<Filter> loginFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
♐ LoginRequest
더보기
@Data
public class LoginRequestDto {
/**
* 이메일.
*/
@NotBlank(message = "이메일 입력은 필수 입니다.")
@Email(message = "이메일 형식으로 입력하세요")
private String email;
/**
* 비밀번호.
*/
@Size(min = 8, message = "비밀번호는 최소 8글자 이상이여야 합니다")
private String password;
}
♐ LoginController
더보기
@Validated
@RestController
@RequiredArgsConstructor
public class LoginController {
private final LoginService loginService;
private final UserService userService;
/**
* 사용자의 로그인 요청을 처리합니다.
*
* @param dto 로그인 요청 데이터
* @param request HTTP 요청 객체
* @return 로그인 응답 데이터와 함께 200 OK 상태 코드 또는 404 Not Found
*/
@PostMapping("/session-login")
public ResponseEntity<LoginResponseDto> login(@Valid @RequestBody LoginRequestDto dto, HttpServletRequest request) {
LoginResponseDto loginResponseDto = loginService.login(dto.getEmail(), dto.getPassword());
if (loginResponseDto.getId() == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
HttpSession session = request.getSession();
UserResponseDto loginUser = userService.findById(loginResponseDto.getId());
session.setAttribute(Const.LOGIN_USER, loginUser);
return new ResponseEntity<>(loginResponseDto, HttpStatus.OK);
}
/**
* 사용자의 로그아웃 요청을 처리합니다.
*
* @param request HTTP 요청 객체
* @return 200 OK 상태 코드
*/
@PostMapping("/session-logout")
public ResponseEntity<Void> logout(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* 이메일의 존재 여부를 확인하는 API입니다.
*
* @param email 확인할 이메일
* @return 이메일의 존재 여부 결과와 함께 200 OK 상태 코드
*/
@GetMapping("/check")
public ResponseEntity<String> check(
@NotBlank(message = "이메일 입력은 필수 입니다.")
@Email
@RequestParam String email) {
String result = loginService.getEmail(email);
return new ResponseEntity<>(result, HttpStatus.OK);
}
}
♐ LoginService
더보기
@Service
@RequiredArgsConstructor
public class LoginService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
/**
* 사용자를 로그인합니다.
*
* @param email 사용자 이메일
* @param password 사용자 비밀번호
* @return 로그인 응답 DTO
* @throws PasswordEqualsCheckException 비밀번호가 일치하지 않을 때 발생
*/
@Transactional
public LoginResponseDto login(String email, String password) {
User user = userRepository.findByEmailOrElseThrow(email);
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new PasswordEqualsCheckException();
}
return new LoginResponseDto(user.getId());
}
/**
* 주어진 이메일의 사용 가능 여부를 확인합니다.
*
* @param email 확인할 이메일
* @return 사용 가능 여부 메시지
*/
@Transactional
public String getEmail(String email) {
if (userRepository.hasSameEmail(email)) {
return "이미 존재하는 이메일 입니다";
} else {
return "사용 가능한 이름 입니다";
}
}
}
✅ 예외처리
- Validation 을 활용
- 정해진 예외 뿐아니라 프로젝트를 분석한 후 커스텀 예외처리
간단한 프로젝트라 정해진 예외에서 대부분 처리가 가능해서 아래의 예제만 분석되었다.
♐ AllExceptionHandler
더보기
package org.example.todojpa.handler;
import jakarta.validation.ConstraintViolationException;
import org.example.todojpa.Exception.EmailFoundException;
import org.example.todojpa.Exception.PasswordEqualsCheckException;
import org.example.todojpa.Exception.NotFoundException;
import org.example.todojpa.dto.ErrorResponseDto;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.stream.Collectors;
@ControllerAdvice
public class AllExceptionHandler {
/**
* 유효성 검사 예외를 처리합니다.
*
* @param ex 처리할 예외
* @return 오류 응답
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponseDto> handleValidationExceptions(MethodArgumentNotValidException ex) {
String messages = ex.getBindingResult().getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return new ResponseEntity<>(new ErrorResponseDto("C001", messages, "BAD_REQUEST"), HttpStatus.BAD_REQUEST);
}
/**
* 제약 조건 위반 예외를 처리합니다.
*
* @param ex 처리할 예외
* @return 오류 응답
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponseDto> handleConstraintViolationException(ConstraintViolationException ex) {
return new ResponseEntity<>(new ErrorResponseDto("C002", ex.getMessage(), "BAD_REQUEST"), HttpStatus.BAD_REQUEST);
}
/**
* 지원하지 않는 HTTP 메서드 예외를 처리합니다.
*
* @param ex 처리할 예외
* @return 오류 응답
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ErrorResponseDto> handleDefaultHandlerExceptionResolver(HttpRequestMethodNotSupportedException ex) {
return new ResponseEntity<>(new ErrorResponseDto("C003", "전송 형식에 오류가 있습니다", "METHOD_NOT_ALLOWED"), HttpStatus.METHOD_NOT_ALLOWED);
}
/**
* 비밀번호 불일치 예외를 처리합니다.
*
* @param ex 처리할 예외
* @return 오류 응답
*/
@ExceptionHandler(PasswordEqualsCheckException.class)
public ResponseEntity<ErrorResponseDto> handlePasswordEqualsCheckException(PasswordEqualsCheckException ex) {
return new ResponseEntity<>(new ErrorResponseDto("C004", ex.getMessage(), "BAD_PASSWORD"), HttpStatus.NOT_ACCEPTABLE);
}
/**
* SQL 무결성 제약 위반 예외를 처리합니다.
*
* @param ex 처리할 예외
* @return 오류 응답
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public ResponseEntity<ErrorResponseDto> handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException ex) {
return new ResponseEntity<>(new ErrorResponseDto("C005", "이미 해당 이메일이 존재합니다", "BAD_REQUEST"), HttpStatus.BAD_REQUEST);
}
/**
* 객체를 찾을 수 없는 예외를 처리합니다.
*
* @param ex 처리할 예외
* @return 오류 응답
*/
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponseDto> handleTodoNotFindException(NotFoundException ex) {
return new ResponseEntity<>(new ErrorResponseDto("C006", ex.getMessage(), "NOT_FOUND"), HttpStatus.NOT_FOUND);
}
/**
* 이메일을 찾을 수 없는 예외를 처리합니다.
*
* @param ex 처리할 예외
* @return 오류 응답
*/
@ExceptionHandler(EmailFoundException.class)
public ResponseEntity<ErrorResponseDto> handleEmailFoundException(EmailFoundException ex) {
return new ResponseEntity<>(new ErrorResponseDto("C007", ex.getMessage(), "FOUND"), HttpStatus.IM_USED);
}
}
♐ TodoRequestDto
더보기
@Data
public class TodoRequestDto {
/**
* 제목.
*/
@Size(min = 2, message = "두글자 이상 입력하세요")
private final String title;
/**
* 내용.
*/
@NotBlank(message = "내용을 입력하세요")
private final String contents;
}
♐ UserRequestDto
더보기
@Data
public class UserRequestDto {
/**
* 사용자 이름.
*/
@NotBlank(message = "이름을 입력하세요")
private final String userName;
/**
* 이메일.
*/
@NotBlank(message = "이메일을 입력하세요")
@Email(message = "이메일 형식으로 입력하세요")
private final String email;
/**
* 비밀번호.
*/
@Size(min = 8, message = "비밀번호는 최소 8글자 이상이여야 합니다")
private final String password;
}
♐ TodoController
더보기
/**
* 새로운 Todo 항목을 저장하는 API입니다.
*
* @param todoRequestDto Todo 요청 데이터
* @param request HTTP 요청 객체
* @return 생성된 Todo의 응답 데이터와 함께 201 Created 상태 코드
*/
@PostMapping
public ResponseEntity<TodoResponseDto> save(@Valid @RequestBody TodoRequestDto todoRequestDto, HttpServletRequest request) {
HttpSession session = request.getSession(false);
UserResponseDto loginUser = (UserResponseDto) session.getAttribute(Const.LOGIN_USER);
TodoResponseDto save = todoService.save(loginUser.getEmail(), todoRequestDto.getTitle(), todoRequestDto.getContents());
return new ResponseEntity<>(save, HttpStatus.CREATED);
}
/**
* Todo 항목을 수정하는 API입니다.
*
* @param id Todo 항목의 ID
* @param todoRequestDto 수정할 Todo 데이터
* @return 수정된 Todo의 응답 데이터와 함께 200 OK 상태 코드
*/
@PatchMapping("/{id}")
public ResponseEntity<TodoResponseDto> updateTodo(@Valid @PathVariable Long id, @RequestBody TodoRequestDto todoRequestDto) {
TodoResponseDto updateTodo = todoService.updateTodo(id, todoRequestDto.getTitle(), todoRequestDto.getContents());
return new ResponseEntity<>(updateTodo, HttpStatus.OK);
}
♐ UserController
더보기
/**
* 새로운 사용자를 등록하는 API입니다.
*
* @param userRequestDto 사용자 요청 데이터
* @return 생성된 사용자의 응답 데이터와 함께 201 Created 상태 코드
*/
@PostMapping("/signup")
public ResponseEntity<UserResponseDto> save(@Valid @RequestBody UserRequestDto userRequestDto) {
UserResponseDto userResponseDto = userService.save(userRequestDto.getUserName(), userRequestDto.getEmail(), userRequestDto.getPassword());
return new ResponseEntity<>(userResponseDto, HttpStatus.CREATED);
}
/**
* 사용자 정보를 수정하는 API입니다.
*
* @param id 사용자 ID
* @param userRequestDto 수정할 사용자 데이터
* @return 수정된 사용자의 응답 데이터와 함께 200 OK 상태 코드
*/
@PatchMapping("/{id}")
public ResponseEntity<UserResponseDto> updateUser(@Valid @PathVariable Long id, @RequestBody UserRequestDto userRequestDto) {
UserResponseDto userResponseDto = userService.updateUser(id, userRequestDto.getUserName(), userRequestDto.getEmail());
return new ResponseEntity<>(userResponseDto, HttpStatus.OK);
}
Login Filter 예외처리시 오류가 발생하여 트러블 슈팅을 작성하였다.
https://computerreport.tistory.com/86
[Spring]Filter 예외처리
intro프로젝트를 진행하며 발생한 문제 상황과 해결 과정들을 상세히 기록하고 추후에 같은 문제가 발생 했을때 빠르게 문제 해결하기 위해 트러블 슈팅을 정리할려고 한다.기록하는 습관을 기
computerreport.tistory.com
'프로젝트' 카테고리의 다른 글
[일정관리JPA] API 명세 및 ERD작성 (0) | 2025.04.04 |
---|