어제 와는 다르게 오늘은 특강/세션이 하나도 없는날이라서 시간이 매우 많았다.
어제 퇴실 직전까지 S.A 피드백을 받고 난후 하루를 마쳤었는데 이제 본격적으로 오늘 부로 각각 팀원 마다 많은 기능을 담당하면서 개발을 진행하게 되었으며, 내가 git에 대한 경험이 GUI 방식으로 하는법을 알아서 직접 나서서 Repository를 만들고 팀원 한명한명씩 이메일을 받아서 초대를 하고, 기초 설정작업을 마친 후 개발 단계에 들어갔다.
분량 상으로 의존성주입, Entity와 Dto는 있다고 가정하고 설명을 하도록 하겠습니다.
( 암호화 처리는 구현한 기능을 설명하면서 부연설명 느낌으로 포스팅 )
공통적으로 Repository는 JpaRepository를 통해 상속을 받아서 자동으로 관리하게 하게 구현하였다.
💡 정규 표현식
@Pattern ( regexp = "" ) 를 사용하면 정규 표현식을 사용 할수 있다.
"^ $" 로 시작과 끝을 의미하며, [A-Z]는 대문자, [a-z]는 소문자, [0-9] 숫자, [가-힣] 한글을 나타낸다.
중괄호로 나타내어져 있는 표현은 {2, 6} 이면 2글자이상 6글자 이하 라는 뜻이며, {8, } 이면 8글자 이상이라고 말한다.
밑에 이메일 필드를 보면 대소문자,숫자 조합 다음 @ 가 필요하고 다시 대소문자, 조합 다음 . 이 필요하고 마지막에는 2글자 6글자 이하로 끝내야한다는 표현식이 있다.
그 밑에 비밀번호 필드는 소문자 + 대문자 + 특수문자를 포함한 8글자 이상 패스워드를 나타내야하는 형식을 나타낸다.
// @Email 은 간단하지만 빈 문자열을 통과 시킨다.(@Pattern을 통한 정규식 검사를 더 많이 사용)
@Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$", message = "이메일 형식이 올바르지 않습니다.")
private final String email;
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>])[A-Za-z\\d!@#$%^&*(),.?\":{}|<>]{8,}$", message = "비밀번호는 8글자 이상, 영어 대문자 + 특수문자 + 숫자 형식만 가능합니다.")
private final String password;
1️⃣ 회원가입 기능
회원가입 기능은 PostMapping 으로 생성을 하는 것이기 때문에 이에 해당한다.
Controller에서 /users/signup 을 통해 회원가입을 진행하며, 요청 Dto에 맞게 진행하고, Service 쪽으로 Signup 메소드를 구현해서 진행하였다. ( 201 Created )
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
// 회원가입
@PostMapping("/signup")
public ResponseEntity<UserResponseDto> Signup(@Valid @RequestBody SignupUserRequestDto requestDto) {
UserResponseDto responseDto = userService.Signup(requestDto.getEmail(), requestDto.getPassword(), requestDto.getName(), requestDto.getAddress(), requestDto.getRole());
return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
}
Service에서는 확장성을 고려해서 인터페이스로 구현하여 implements로 구현체를 받는 식으로 하였다.
@Transactional을 통해 트랜잭션 처리를 하고, 비밀번호는 암호화처리를 해야하기 때문에 PasswordEncoder 클래스에 encode( ) 메소드를 활용하여 요청받은 password를 매개변수에 넣고, 암호화 처리를 하고난 후 User은 객체를 생성하여 회원가입이 되도록 구현하였다.
// 회원 가입
@Transactional
@Override
public UserResponseDto Signup(String email, String password, String name, String address, UserRole role) {
// 비밀번호 암호화
String encodePassword = passwordEncoder.encode(password);
User addUser = new User(email, encodePassword, name, address, role);
User savedUser = userRepository.save(addUser);
return new UserResponseDto(
savedUser.getUserId(),
savedUser.getEmail(),
savedUser.getName(),
savedUser.getAddress(),
savedUser.getRole(),
savedUser.getCreatedAt(),
savedUser.getUpdatedAt());
}
2️⃣ 전체 유저 조회
Controller 쪽에서는 JPA에 findAll()을 활용하기 위해 Service에서 findAll() 그대로 활용하여 메소드를 구현하였으며,
기본적 CRUD의 R기능을 하였다. ( 200 OK )
// 전체 유저 조회
@GetMapping
public ResponseEntity<List<UserResponseDto>> findAll(){
List<UserResponseDto> responseDtoList = userService.findAll();
return new ResponseEntity<>(responseDtoList, HttpStatus.OK);
}
Service에서 JPA의 findAll()을 활용하고, 기존에 꾸준히 해왔었던 개인 프로젝트 때 처럼 CRUD로 List 형태로 userRepository.stream().map(UserResponse::toDto).toList(); 현재 가입이 된 유저들을 리스트형태로 나열하게 구현했다.
// 전체 유저 조회
@Override
public List<UserResponseDto> findAll() {
return userRepository.findAll()
.stream()
.map(UserResponseDto::toDto)
.toList();
}
toDto는 UserResponseDto에서 가지고 있는 필드명으로 static으로 선언하여 static은 프로그램이 중지 될때 까지 메모리에 살아남아 있다. 그래서 새로운 객체를 만들어내고 반환 받는 값으로 구현을 하였다.
public static UserResponseDto toDto(User user) {
return new UserResponseDto(
user.getUserId(),
user.getEmail(),
user.getName(),
user.getAddress(),
user.getRole(),
user.getCreatedAt(),
user.getUpdatedAt()
);
}
3️⃣ 회원 정보 수정
Controller 에서는 회원정보 수정으로 userId 값을 받아와야 하기 때문에 userId url을 추가하여 Service에 update 메소드로 구현을 하고 수정해야할 requestDto 까지 받아와서 Service 단으로 넘기는 방식으로 구현하였다. ( 200 OK )
// 회원 정보 수정
@PatchMapping("/{userId}")
public ResponseEntity<UserResponseDto> update(
@PathVariable Long userId,
@RequestBody UpdateUserRequestDto requestDto
) {
UserResponseDto responseDto = userService.update(userId, requestDto.getOldPassword(), requestDto.getNewPassword(), requestDto.getAddress(), requestDto.getRole());
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
Service에서 예전에 사용한것 처럼 User엔티티에 update메소드를 직접 구현해서 하였고, 그 전에 가장 먼저 DB에 저장이 되어 있는지 findByIdOrElseThrow를 통해 유저 id값을 찾고 ( 없으면 예외처리는 userRepository 내 default문으로 구현 ), 기존 비밀번호와 요청한 비밀번호가 일치한지 여부를 판단하였는데 (트러블 슈팅에 자세히 얘기 할 예정) 여러 과정을 거쳐서 해결하고, 수정할 비밀번호를 암호화 해준다음 flush() 라는 메소드를 몰랐을 당시에는 수정과 동시에 수정일이 갱신이 되지 않는 현상이 발생하여서 이 메소드가 끝나고 조회할때는 이미 되어있기는 하지만, 보기가 좋지 않아서 튜터님에게 찾아가 flush라는 것을 다시 알게 되었다. ( 직접 사용을 해보는것은 처음 )
@Transactional
@Override
public UserResponseDto update(Long userId, String oldPassword, String newPassword, String address, UserRole role) {
User findUser = userRepository.findByIdOrElseThrow(userId);
/**
* 비밀번호가 암호화 되어 있기 때문에 요청할 oldPasswor와 finduser로 찾은 비밀번호를
* 비교해주는 matches 메소드를 활용 (트러블 슈팅)
*/
if(!passwordEncoder.matches(oldPassword, findUser.getPassword())) {
throw new RuntimeException("기존 비밀번호가 일치하지 않습니다.");
}
// 비밀번호 암호화
String encodePassword = passwordEncoder.encode(newPassword);
findUser.update(encodePassword, address, role);
// DB에 실제로 반영하는 역할 (flush()) 중간에 DB에 저장해주는역할
userRepository.flush();
return new UserResponseDto(
findUser.getUserId(),
findUser.getEmail(),
findUser.getName(),
findUser.getAddress(),
findUser.getRole(),
findUser.getCreatedAt(),
findUser.getUpdatedAt()
);
}
📃 깨달은점
- 처음에 password를 응답 코드에 노출 하는 식으로 개발을 진행하여서 튜터님에게 조언을 받았다. 암호화가된 password도 눈에 보여지면 좋은 것이 아니기 때문에 개인정보 노출이 될 수 있다는 조언을 해주셨다. 그래서 password는 응답 코드에는 작성하지 않는 방식으로 개선해 나가야 겠다는 생각이 들었다.
- flush() 를 통해서 DB에 변경된 데이터를 반영 시킬 수 있다는 점을 배웠다.
'[내일배움캠프-Sparta] > Spring 6기' 카테고리의 다른 글
| TIL 45 [ Spring 심화 프로젝트 ( Day 4 ) - 구현 ( JWT 로그인 필터 기능 ), PR 리뷰/병합 ] (0) | 2025.04.25 |
|---|---|
| TIL 44 [ Spring 심화 프로젝트 ( Day 3 ) - 구현 ( 회원탈퇴, JWT 로그인 기능 ) ] (0) | 2025.04.24 |
| TIL 42 - [ Spring 심화 프로젝트 ( Day 1 ) - 설계 ( 와이어프레임, ERD, API 명세서 ) ] (0) | 2025.04.22 |
| 테스트 코드 ( 이론 ) (0) | 2025.04.21 |
| TIL 41 - [ Spring 심화 ( API 예외처리, JPQL, Fetch Join ) ] (0) | 2025.04.21 |